TypeScript, 'Parse, Don't Validate' 원칙 적용의 딜레마
TypeScript에서 'Parse, Don't Validate' 원칙 적용의 어려움과 브랜디드 타입(Branded Types) 활용법을 설명함.
TypeScript의 구조적 타이핑(Structural Typing)이 원칙 적용을 방해하며, 데이터 격리(Data Isolation)의 중요성을 강조함.
커뮤니티에서는 대안 언어(Alternative Languages) 사용 및 TypeScript의 실질적 안전성(Real Safety)에 대한 논쟁이 활발함.
TypeScript의 구조적 타이핑과 'Parse, Don't Validate'의 충돌
본문에서는 TypeScript의 구조적 타이핑(Structural Typing)이 'Parse, Don't Validate' 원칙을 따르기 어렵게 만든다고 지적합니다. 동일한 구조를 가진 타입은 같은 것으로 간주되므로, `string` 타입 내에서 이메일 주소(`EmailAddress`)와 같은 특정 의미를 가진 타입을 구분하기 어렵다는 것입니다. 이로 인해 개발자는 데이터 유효성 검증(Data Validation) 로직을 코드 곳곳에 반복적으로 작성하게 되며, 이는 타입 시스템의 정보 손실(Information Loss)을 야기한다고 설명합니다.
브랜디드 타입(Branded Types)을 통한 타입 안전성 확보
이러한 문제를 해결하기 위해 커뮤니티에서는 브랜디드 타입(Branded Types) 또는 태깅(Tagging) 기법을 사용한다고 소개합니다. 이는 실제 런타임에는 존재하지 않는 고유 심볼(Unique Symbol)이나 문자열 리터럴(String Literal)을 타입에 추가하여, 컴파일 타임에만 타입 간의 호환성을 제한하는 방식입니다. 이를 통해 `string` 타입과 `Email` 타입을 명확히 구분하고, 불가능한 상태를 표현 불가능하게(Unrepresentable States) 만들어 타입 안전성을 높일 수 있다고 설명합니다.
파서(Parser)와 검증자(Validator)의 근본적 차이
논의에서는 검증자(Validator)가 단순히 데이터의 유효성을 확인하고 정보를 버리는 반면, 파서(Parser)는 데이터를 더 정제된 타입으로 변환하고 그 결과를 타입 시스템에 인코딩한다고 강조합니다. 즉, 파서는 타입 자체를 증명(Type as Proof)으로 활용하여 코드의 신뢰도를 높이는 반면, 검증자는 런타임에만 유효하며 타입 시스템에는 반영되지 않는다고 지적합니다. 이는 타입 시스템의 정보 활용(Leveraging Type System Information) 측면에서 큰 차이를 만든다고 설명합니다.
TypeScript의 한계와 대안 언어/라이브러리
커뮤니티에서는 TypeScript의 이러한 한계 때문에 Haskell, Elm, F#과 같은 함수형 프로그래밍 언어가 'Parse, Don't Validate' 원칙에 더 적합하다고 언급합니다. 또한, Zod, io-ts와 같은 라이브러리가 TypeScript에서 파싱을 돕지만, 근본적인 마인드셋 변화(Mindset Shift) 없이는 효과를 보기 어렵다는 의견도 제시됩니다. 일부 사용자는 컴파일 투 JS(Compile-to-JS) 언어 사용을 제안하지만, 기존 코드베이스와 팀의 전문성 때문에 현실적으로 어렵다는 반론도 존재합니다.
실제 코드베이스에서의 적용 및 커뮤니티 논쟁
실제 코드베이스에서는 브랜디드 타입의 복잡성, 타입 단언(Type Assertion)의 남용 가능성, 그리고 불완전한 타입 검사(Incomplete Type Checking)로 인해 'Parse, Don't Validate' 원칙 적용이 어렵다는 의견이 많습니다. 특히 주니어 개발자가 `Option` 타입의 `.value`에 직접 접근하여 null 체크를 하는 등, 의도치 않은 동작(Unintended Behavior)이 발생하여 코드 가독성과 유지보수성을 해칠 수 있다는 경험담이 공유되었습니다. 이는 TypeScript가 실질적인 안전성(Real Safety)을 제공하는지에 대한 근본적인 질문을 던집니다.