Search
2️⃣

Pattern 2. Parse, Don't Validate

1. ?.if 는 믿을 수 없다

if (data && data.user && data.user.profile) // ... const name = response.data?.name; // ...
TypeScript
복사
이런 코드를 작성할 때, 우리는 '안전한' 코드를 짜고 있다고 생각합니다. 하지만 실제로는 코드의 모든 경계에서 "이 값이 정말 있을까?"라는 불안감과 싸우고 있는 것입니다.
우리는 코드 전체에 걸쳐 수많은 if문과 옵셔널 체이닝(?.)이라는 '방어벽'을 쌓아 올리며, 끊임없이 여러 가능성을 동시에 생각해야 하는 인지적 대가를 치릅니다.
이것은 '한번에 하나씩' 원칙의 정반대입니다. 코드의 모든 부분이 수많은 불확실한 시나리오를 걱정하고 있기 때문입니다. 'Parse, Don't Validate' 패턴은 이 소모적인 방어전을 끝내는 가장 강력한 전략입니다.

2. 원칙: 의심하지 말고, 보장하라

이 패턴의 핵심 아이디어는 간단합니다. 일종의 ‘얼리 리턴(Early Return)’ 이기도 합니다.
데이터를 사용하는 모든 곳에서 의심하며 검증(Validate)하지 말고, 데이터가 우리 시스템에 들어오는 가장 첫 관문에서 우리가 원하는 형태로 파싱(Parse)하라. 그 이후의 모든 곳에서는 타입과 구조를 100% 보장할 수 있다.
'Validate'는 "여권 좀 보여주시겠어요?"라며 매번 확인만 하는 것이고, 'Parse'는 입국 심사대에서 여권을 확인한 뒤, 우리 시스템 안에서는 절대적으로 신뢰할 수 있는 '출입증'을 새로 발급해주는 것과 같습니다.
한번 출입증을 발급받으면, 내부의 모든 곳에서는 더 이상 여권을 검사할 필요가 없습니다.
이 원칙을 통해 우리는 불확실성을 시스템의 가장 바깥 경계, 즉 '현관'에 격리시킵니다. 현관을 통과한 데이터는 모두 '믿을 수 있는 시민'이 됩니다.

3. Trigger: 언제, 어떻게 '입국 심사대'를 세워야 할까?

내 코드가 불확실성으로 가득 차 있다는 '신호(Trigger)'는 명확합니다.
?.if (value) 가 코드 전체로 퍼져나가고 있을 때
"Cannot read property 'xxx' of undefined" 런타임 에러를 자꾸 마주치게 될 때
API 응답 값의 타입이 string일지 number일지 몰라 == 비교를 쓰고 있을 때
개선 예시

4. Ability: 다양한 경계에서 불확실성 차단하기

이제 실제로 코드의 여러 '경계'에서 파싱을 통해 불확실성을 어떻게 차단하는지 보겠습니다.
Case 1: 라우터 파라미터
URL 파라미터의 값을 컴포넌트의 가장 첫 줄에서 파싱합니다.
파싱에 실패하면 에러 바운더리가 처리할 것이고, 성공하면 컴포넌트의 나머지 부분은 customerIdnumber 타입임을 100% 확신할 수 있습니다.
// Before const { customerId } = useParams() as string const numId = Number(customerId) if (!customerId || isNaN(numId)) //...
TypeScript
복사
// After 1 - throw const useCustomerId = () => { const { customerId } = useParams() if (!customerId || Number.isNaN(Number(customerId))) { throw new Error('올바르지 않은 형식의 파라미터입니다') } return customerId }
TypeScript
복사
// After 2 - zod const PathParams = z.object({ customerId: z.coerce.number() }) const { customerId } = PathParams.parse(useParams())
TypeScript
복사
Case 2: API 응답 (feat. React Query)
useQuerydataundefined일 수 있지만, useSuspenseQuery는 데이터가 없을 경우 컴포넌트 렌더링을 중단시키고 Suspense fallback을 보여줍니다.
즉, 컴포넌트 본문이 실행되는 시점에는 data가 항상 존재함을 보장하여, 불필요한 if문과 옵셔널 체이닝을 제거합니다.
// Before const { data, isLoading, isError } = useQuery() if (isLoading) // ... if (isError) // ... if (data) //... const price = data?.product?.price // number | undefined (핸들러 함수 등)
TypeScript
복사
// After const { data } = useSuspenseQuery() const price = data.product.price // data.product.price 확실히 존재!
TypeScript
복사
Case 3: Form 입력
폼을 다룰 때 가장 흔한 실수는 handleSubmit 함수 안에 온갖 책임을 다 섞어 넣는 것입니다.
이 함수는 (1) 사용자의 입력을 가져와서, (2) 모든 규칙에 맞는지 검증하고, (3) 에러가 있으면 상태를 업데이트하고, (4) 성공하면 서버로 제출하는 등 너무 많은 일을 동시에 합니다. 검증 규칙이 늘어날수록 이 함수는 거대한 '괴물'이 되어, 유지보수와 테스트가 불가능한 지경에 이릅니다.
Before: 모든 책임이 뒤섞인 '괴물' 핸들러 함수
After: '검증'과 '제출'의 역할을 명확히 분리하기
Case 4: 환경 변수, 쿼리 스트링, localStorage 등 이 원칙은 신뢰할 수 없는 모든 종류의 데이터에 적용할 수 있습니다. zod와 같은 스키마 라이브러리를 활용하면, 애플리케이션의 모든 경계에서 불확실성을 체계적으로 제거할 수 있습니다.
URL 파싱
환경변수 파싱
localStorage 파싱
핵심: Parse, Don't Validate는 단순히 코드를 깔끔하게 만드는 기술이 아닙니다. 이것은 불확실성을 대하는 우리의 자세를 '방어'에서 '통제'로 바꾸는 철학입니다.
코드의 현관문을 튼튼히 지킴으로써, 우리는 집 안에서는 안심하고 가장 중요한 핵심 비즈니스 로직, 즉 '단 하나'의 일에만 집중할 수 있게 됩니다.

참고 자료