Go 1.19 제네릭 실전 도입기 - 유틸 함수 리팩토링
배경
회사 서비스의 백엔드 API 서버를 Go로 운영 중이다. 슬라이스를 다루는 유틸 함수들이 각 타입별로 중복 구현되어 있었는데, Go 1.18부터 제네릭이 정식 지원되면서 리팩토링할 시점이 왔다고 판단했다.
기존 코드의 문제
각 타입마다 Map, Filter 같은 함수를 별도로 구현하고 있었다.
func MapStrings(arr []string, fn func(string) string) []string {
result := make([]string, len(arr))
for i, v := range arr {
result[i] = fn(v)
}
return result
}
func MapInts(arr []int, fn func(int) int) []int {
result := make([]int, len(arr))
for i, v := range arr {
result[i] = fn(v)
}
return result
}
제네릭 적용
Go 1.19로 업그레이드 후 제네릭으로 통합했다.
func Map[T any, R any](arr []T, fn func(T) R) []R {
result := make([]R, len(arr))
for i, v := range arr {
result[i] = fn(v)
}
return result
}
func Filter[T any](arr []T, predicate func(T) bool) []T {
result := make([]T, 0)
for _, v := range arr {
if predicate(v) {
result = append(result, v)
}
}
return result
}
사용 예시:
userIDs := Map(users, func(u User) int64 { return u.ID })
activeUsers := Filter(users, func(u User) bool { return u.IsActive })
성능 비교
벤치마크를 돌려봤는데, 제네릭 버전이 interface{} 기반 구현보다 약 30% 빨랐다. 타입 단언 오버헤드가 없어지면서 얻은 이득이다.
주의사항
- comparable 제약이 필요한 경우(맵 키 등)는
any대신comparable사용 - 제네릭 함수는 인라인이 안 되는 경우가 있어 핫 패스에서는 신중히 판단
- 기존 코드와의 호환성을 위해 점진적으로 마이그레이션
결론
3개월간 운영하면서 코드 중복이 크게 줄었고, 타입 안정성도 향상됐다. Go의 제네릭은 Java나 TypeScript만큼 유연하진 않지만, 실용적인 수준에서 충분히 유용했다.