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