타입 시스템을 활용하세요

1 day ago 3

  • 프로그래밍 시 타입 시스템을 활용하여 서로 다른 데이터 의미를 명확히 구분할 수 있음
  • 문자열이나 정수처럼 일반적인 타입을 그대로 사용하는 것은 맥락을 잃게 하며 버그로 이어질 수 있음
  • 동일한 기반 타입이더라도 목적에 맞게 새로운 타입을 정의하면 컴파일 타임 오류로 실수를 방지 가능
  • Go 라이브러리 libwx에서는 측정 단위를 명확히 구분하는 타입들을 정의해 float64 혼용에 의한 실수를 방지
  • 예시 코드에서 UUID 타입을 UserID와 AccountID로 분리해 잘못된 사용을 컴파일러가 차단
  • Go처럼 타입 시스템이 강하지 않은 언어에서도 간단한 타입 래핑으로 버그를 예방할 수 있음

타입 시스템을 적극적으로 활용하자

문제의 출발점: 단순 타입의 혼용

  • 프로그래밍에서는 string, int, UUID 같은 기본 타입만으로 많은 값을 표현하는 경우가 많음
  • 하지만 프로젝트 규모가 커지면 이런 단순 타입이 서로 구분 없이 혼용되어 사용되는 실수가 잦아짐
    • 예: userID 문자열을 실수로 accountID로 넘기거나, int 인자가 3개 있는 함수에서 순서를 잘못 넘기는 실수 등

해결책: 의도를 드러내는 타입 정의

  • int나 string은 빌딩 블록일 뿐, 시스템 전반에 그대로 넘기면 의미 있는 맥락이 사라짐
  • 이를 방지하려면 역할별로 고유한 타입을 정의해서 사용해야 함
    • 예: 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 라이브러리

  • 필자는 자신의 Go 라이브러리 libwx에서 이 기법을 실천 중
  • 모든 측정 단위에 대해 전용 타입을 정의하고, 단위 변환 메서드도 타입에 연결
    • 예: Km.Miles() 메서드를 통해 단위를 명확히 구분함
  • 아래는 잘못된 함수 인자 순서와 단위 혼동을 컴파일러가 차단하는 예시: // 화씨 온도 선언 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)) // 컴파일러가 인자 타입 오류를 막아줌
  • 단순히 float64를 썼다면 발생할 수 있는 실수들을 모두 예방 가능

결론: 타입 시스템을 적극 활용하자

  • 타입 시스템은 단순히 문법 검사용이 아니라 버그 예방 도구
  • 모델마다 ID 타입을 따로 정의하고, 함수 인자도 float이나 int 대신 명확한 타입으로 감싸야 함
  • 이 방식은 Go처럼 타입 시스템이 강하지 않은 언어에서도 매우 효과적이며 구현도 간단
  • 현실에서는 UUID나 문자열 타입 혼용에 의한 버그가 정말 많음
  • 이 간단한 방식이 생산 코드에서 흔히 사용되지 않는 현실이 놀랍다고 저자는 강조함

관련 코드

Read Entire Article