TypeScript 타입 가드로 런타임 안정성 확보하기

문제 상황

외부 API 연동 작업 중 프로덕션에서 Cannot read property 'id' of undefined 에러가 간헐적으로 발생했다. TypeScript를 사용하고 있었지만, API 응답이 예상과 다른 형태로 오는 경우를 제대로 처리하지 못했다.

interface User {
  id: number;
  name: string;
  email?: string;
}

const response = await fetch('/api/user');
const user: User = await response.json(); // 타입 단언만 하고 검증은 없음
console.log(user.id.toFixed()); // 런타임 에러 가능

타입 가드 도입

TypeScript의 타입 가드를 사용해 런타임에서 실제 타입을 검증하도록 수정했다.

function isUser(obj: any): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    typeof obj.id === 'number' &&
    typeof obj.name === 'string' &&
    (obj.email === undefined || typeof obj.email === 'string')
  );
}

const response = await fetch('/api/user');
const data = await response.json();

if (isUser(data)) {
  console.log(data.id.toFixed()); // 안전하게 사용
} else {
  throw new Error('Invalid user data');
}

유틸리티 함수로 확장

반복적인 검증 로직을 재사용할 수 있도록 제네릭 유틸리티를 만들었다.

function validateResponse<T>(
  data: any,
  validator: (obj: any) => obj is T
): T {
  if (!validator(data)) {
    throw new Error('Response validation failed');
  }
  return data;
}

// 사용
const user = validateResponse(data, isUser);

결과

타입 가드 도입 후 런타임 에러가 명확한 검증 에러로 바뀌어 디버깅이 쉬워졌다. API 스펙 변경이나 예외 상황을 조기에 발견할 수 있게 되었고, 에러 로그도 구체적으로 남길 수 있게 되었다.

TypeScript의 타입 시스템은 강력하지만 컴파일 타임에만 동작한다는 한계가 있다. 외부 데이터를 다룰 때는 런타임 검증을 함께 구현하는 것이 필수다.

TypeScript 타입 가드로 런타임 안정성 확보하기