Chapter 4

타입 설계

  • 4.1 유효한 상태만 표현하는 타입을 지향하기
  • 4.2 엄격하게 생성하고 너그럽게 사용하기
  • 4.3 문서에 타입 정보를 쓰지 않기
  • 4.4 null과 undefined를 타입에서 구분하기
  • 4.5 유니온의 인터페이스보다 인터페이스의 유니온 사용하기
  • 4.6 string보다 더 구체적인 타입 사용하기
  • 4.7 선택적 속성보다 분리된 타입 지향하기

타입 설계를 잘하면 분기문과 방어 코드가 줄어들어. 코드의 안전성과 유지보수성은 결국 어떤 타입을 만드느냐에 달려 있거든.

4장을 관통하는 핵심 원칙은 이거야 — 유효하지 않은 상태를 타입이 허용하면, 그 상태에 대한 방어 코드가 필요해지고, 그 방어 코드에서 버그가 생긴다. isLoadingtrue이면서 error가 있는 상태? 말이 안 되는데 타입은 허용하니까 코드에서 처리해야 하지. 태그드 유니온을 써서 RequestPending, RequestError, RequestSuccess 세 가지 상태만 표현하면, "로딩 중이면서 에러"인 상태는 타입 수준에서 존재할 수 없어.

로버스트니스 원칙(Postel's Law)도 타입에 적용돼 — "보내는 건 엄격하게, 받는 건 너그럽게." 함수의 매개변수 타입은 넓게 잡아서 다양한 입력을 받고, 반환 타입은 좁게 잡아서 사용하는 쪽이 정확히 뭘 받는지 알 수 있게 하라는 거야. 입력용 타입과 출력용 타입을 별도로 만들게 되는 경우가 많은데, 귀찮아 보여도 API의 유연성과 안전성을 동시에 잡는 핵심 패턴이야.

주석에 타입 정보를 쓰면 안 돼. 타입 정보는 이미 코드에 있고, 코드가 바뀌었을 때 주석이 안 바뀌면 거짓말하는 주석이 되거든. 주석에는 의도나 제약조건, 단위 정보 같은 타입으로 표현할 수 없는 것을 써야 해. 변수 이름에도 ageNum, nameString 같은 타입 정보를 넣지 마.

nullundefined는 **"전부 있거나 전부 없거나"**를 타입으로 표현해야 해. 함수가 [min, max]를 반환하는데 둘 다 undefined일 수 있으면, 차라리 [number, number] | null로 반환하는 게 부분적으로 초기화된 상태를 원천 차단하는 거야.

유니온의 인터페이스보다 인터페이스의 유니온을 써야 해. type'fill' | 'line' | 'point'이고 width?, radius?가 선택적인 하나의 인터페이스보다, FillLayer, LineLayer, PointLayer를 각각 만들고 유니온으로 묶는 게 유효하지 않은 조합을 원천 차단해.

string은 거의 항상 너무 넓어. recordingType: string 대신 type RecordingType = 'live' | 'studio'로 하면 유효한 값만 허용되고, 자동완성이 동작하고, 오타를 컴파일 타임에 잡을 수 있어. keyof와 함께 쓰면 더 강력해지지.

선택적 속성(?)이 많은 인터페이스도 의심해봐야 해. 선택적 속성이 N개 있으면 가능한 상태가 2^N개거든. 그중 유효한 상태가 몇 개인지 생각해보고, 함께 있어야 하는 속성들을 그룹으로 묶어서 유효한 상태만 표현하는 타입을 설계해야 해.


정리

4장 읽고 기억할 거 세 가지:

  1. 유효한 상태만 표현하는 타입을 만들어라. 유효하지 않은 상태를 타입이 허용하면, 그걸 방어하는 코드에서 버그가 난다.
  2. 인터페이스의 유니온이 유니온의 인터페이스보다 낫다. 태그드 유니온으로 각 상태를 분리하면 선택적 속성의 함정을 피할 수 있다.
  3. string은 거의 항상 너무 넓다. 리터럴 유니온이나 keyof 등으로 더 구체적인 타입을 쓰라.