타입 시스템을 활용하세요

11 hours ago 2

  • 프로그래밍 시 타입 시스템을 활용하여 서로 다른 데이터 의미를 명확히 구분할 수 있음
  • 예를 들어 사용자의 ID, 계정의 ID 등 의미가 다른 값에 별도의 타입을 정의하면 실수를 줄일 수 있음
  • Golang 라이브러리 libwx는 각 측정 단위마다 고유 타입을 정의하여 단위 혼동을 방지함
  • 함수 인자에 잘못된 타입을 전달하면 컴파일 타임에 오류를 발생시켜 런타임 버그를 예방함
  • 타입 시스템을 적극 사용하면 문맥 상 혼동 및 치명적 버그를 극적으로 줄일 수 있음

타입 시스템의 중요성

프로그래밍에서는 int, string, UUID와 같은 기본 내장 타입 또는 라이브러리 제공 타입을 자주 사용함. 하지만 이처럼 일반적인 타입만 활용하면 실제 값이 무엇을 의미하는지 점차 망각하게 되고, 중대한 버그로 이어질 수 있음.

아무리 단순하거나 쉽게 보이는 값이라도, 목적에 따라 별도의 고유한 타입을 정의해서 사용하는 것이 훨씬 좋은 해결책임. 예를 들어 int나 string 등을 직접 전달하지 말고, 각 의미에 맞는 타입을 만드는 방식을 추천함.

예시: ID 타입 분리

  • 타입 구분 없는 UUID 사용 시, 사용자 ID와 계정 ID가 혼동되어 함수에 잘못 전달될 위험이 생김
  • 고유 타입을 정의하면, 아래와 같이 컴파일 타임에 잘못된 인자 전달을 즉시 차단할 수 있음
type AccountID uuid.UUID type UserID uuid.UUID func UUIDTypeMixup() { { userID := UserID(uuid.New()) DeleteUser(userID) // 에러 없음 } { accountID := AccountID(uuid.New()) DeleteUser(accountID) // 에러: AccountID 타입을 UserID로 사용할 수 없음 } { accountID := uuid.New() DeleteUserUntyped(accountID) // 컴파일 타임 에러 없음, 런타임에 문제가 발생할 가능성 높음 } }

libwx에서의 적용

  • 필자는 이전 블로그, “String is not a sufficient type”에서 이와 같은 기법을 강조함
  • libwx(Golang용 기상 및 대기 계산 라이브러리)는 각각의 단위(예: Km, TempF 등)에 독립적인 타입을 정의함
  • 각 단위별로 타입을 분리해, float64만 사용했을 때 발생할 수 있는 치명적인 단위 혼동을 컴파일 단계에서 방지함

코드 예시

// 화씨 온도 선언 temp := libwx.TempF(84) // 상대습도 선언(퍼센트) humidity := libwx.RelHumidity(67) // 화씨 대신 섭씨 온도를 요구하는 함수에 잘못 전달 fmt.Printf("Dew point: %.1fºF\n", libwx.DewPointC(temp, humidity)) // 컴파일러가 타입 mismatch 오류를 바로 검출 // temp (TempF 타입)는 TempC로 사용할 수 없음 // 함수에 인자 순서 잘못 전달 fmt.Printf("Dew point: %.1fºF\n", libwx.DewPointF(humidity, temp)) // 컴파일러가 인자 타입 오류를 막아줌

결론

타입 시스템은 코드 안전성을 지키는 강력한 도구임. 모델마다 고유의 ID 타입을 정의하고, float나 int 등만으로 인자를 처리하는 패턴을 피하는 것이 여러 버그를 예방하는 핵심임.

실제 시스템에서 서로 의미가 다른 int, string, UUID를 혼동해 생긴 버그 사례가 아주 많음. Go처럼 타입 시스템이 비교적 단순한 언어라도, 독립 타입만 잘 정의하면 이런 오동작을 원천적으로 차단할 수 있음. 오히려 이러한 방식을 사용하지 않는 것이 신기하게 느껴질 정도임.

코드 예시는 모두 GitHub에서 확인할 수 있음: https://github.com/cdzombak/libwx_types_lab?ref=dzombak.com

Read Entire Article