Go 1.22 generics로 슬라이스 유틸리티 함수 리팩토링
문제 상황
프로젝트 곳곳에서 슬라이스를 변환하거나 필터링하는 코드가 중복됐다. Go 1.18 이전에는 제네릭이 없어서 interface{}를 쓰거나 타입별로 함수를 만들어야 했는데, 이제는 제네릭을 써볼 만한 시점이라고 판단했다.
기존 코드
func FilterStrings(items []string, fn func(string) bool) []string {
result := []string{}
for _, item := range items {
if fn(item) {
result = append(result, item)
}
}
return result
}
func FilterInts(items []int, fn func(int) bool) []int {
// 동일한 로직 반복
}
타입마다 함수를 만들어야 해서 유지보수가 번거로웠다.
제네릭 적용
func Filter[T any](items []T, fn func(T) bool) []T {
result := make([]T, 0, len(items))
for _, item := range items {
if fn(item) {
result = append(result, item)
}
}
return result
}
func Map[T any, U any](items []T, fn func(T) U) []U {
result := make([]U, len(items))
for i, item := range items {
result[i] = fn(item)
}
return result
}
사용 예시:
ids := []int{1, 2, 3, 4, 5}
even := Filter(ids, func(n int) bool { return n%2 == 0 })
users := []User{{ID: 1}, {ID: 2}}
userIDs := Map(users, func(u User) int { return u.ID })
성능 체크
벤치마크 결과 제네릭 버전이 interface{} 버전보다 약 30% 빠르게 나왔다. 타입 어서션 오버헤드가 없어진 덕분이다.
주의사항
- 컴파일 타임은 소폭 증가했다
- 너무 복잡한 제네릭 제약은 오히려 가독성을 해친다
- 간단한 로직은 그냥 인라인으로 쓰는 게 나을 수 있다
결과적으로 코드 중복은 줄고 타입 안정성은 높아졌다. 앞으로 유사한 유틸리티는 제네릭을 우선 고려할 예정이다.