Go 1.19 제네릭 도입 후 공통 유틸 리팩토링

배경

사내 API 서버는 Go 1.17로 운영 중이었다. 슬라이스 필터링, 맵 변환 같은 유틸 함수가 타입별로 중복 구현되어 있었고, 인터페이스 기반 추상화는 타입 안정성이 떨어졌다. Go 1.19로 업그레이드하면서 제네릭을 적용해봤다.

기존 코드의 문제

// 각 타입마다 별도 함수 필요
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
}

// 사용
activeUsers := Filter(users, func(u User) bool {
    return u.Active
})

제약 조건 활용

숫자 타입 처리가 필요한 경우 커스텀 제약을 만들었다.

type Number interface {
    ~int | ~int64 | ~float64
}

func Sum[T Number](items []T) T {
    var sum T
    for _, v := range items {
        sum += v
    }
    return sum
}

실제 적용 결과

  • 유틸 패키지 코드 라인 수 40% 감소
  • 타입 안정성 확보 (컴파일 타임 체크)
  • 러닝 커브는 있었지만 팀 내 빠르게 적응

주의사항

제네릭이 만능은 아니다. 성능 크리티컬한 부분은 벤치마크 필수다. 초기 컴파일 시간이 약간 늘었고, 일부 IDE 지원이 완벽하지 않았다. 하지만 코드 중복 제거와 유지보수성 향상 효과가 더 컸다.

내년에는 표준 라이브러리에 제네릭 기반 슬라이스, 맵 유틸이 추가될 예정이라고 하니 기대된다.