앞선 글에서 타입 리뷰 체크리스트를 정리했다면, 이제 자연스럽게 이런 요구가 생깁니다. “이 기준을 실제 코드에 어떻게 적용해야 하는가?”입니다.
실무에서는 대부분 이런 상태의 코드를 마주합니다.
- 이미 동작하고 있어서 쉽게 손대기 어렵다
- 타입이 불편하지만, 어디가 문제인지 명확하지 않다
- 한 번에 고치려다 리팩터링 범위가 커진다
추상적인 원칙 설명을 최소화하고, 실제 서비스 코드에서 자주 등장하는 타입을 단계적으로 개선해 나가는 과정을 예제로 보여주는 것에 집중합니다.
예제 배경: 흔히 볼 수 있는 서비스 코드
다음은 실무에서 매우 흔한 서비스 함수 시그니처입니다.
async function getUser(id: string): Promise<any> {
// ...
}
처음에는 빠르게 만들기 좋지만, 이 타입은 아무 정보도 제공하지 않습니다.
- 실패 가능 여부를 알 수 없다
- 반환 데이터 구조를 알 수 없다
- 호출부가 실수해도 컴파일 단계에서 막히지 않는다
1단계: 반환 구조부터 고정한다
가장 먼저 해야 할 일은 “반환값의 형태를 숨기지 않는 것”입니다.
type User = {
id: string;
email: string;
};
async function getUser(
id: string,
): Promise<User | null> {
// ...
}
이 단계에서 얻는 효과는 명확합니다.
- 데이터 구조가 드러난다
- null 처리 필요성이 호출부에 강제된다
2단계: 실패를 값으로 분리한다
User | null 구조는 단순하지만, 실패 이유를 알 수 없다는 한계가 있습니다.
type UserError =
| { type: 'NOT_FOUND' }
| { type: 'UNAUTHORIZED' };
type Result<T, E> =
| { ok: true; data: T }
| { ok: false; error: E };
async function getUser(
id: string,
): Promise<Result<User, UserError>> {
// ...
}
이제 호출부에서는
- 실패 가능성을 무시할 수 없고
- 실패 유형에 따라 분기 처리가 가능해집니다
3단계: 도메인과 규칙 타입을 분리한다
이 시점에서 중요한 점은 User 타입에는 어떤 제네릭도 붙지 않았다는 것입니다.
도메인 타입은 끝까지 단순하게 유지하고, 규칙은 바깥에서 감쌉니다.
type WithAudit<T> = T & {
audit: {
requestId: string;
};
};
async function getUser(
id: string,
): Promise<Result<WithAudit<User>, UserError>> {
// ...
}
이 타입 시그니처만 보고도 다음을 알 수 있습니다.
- 정상/실패 흐름이 존재한다
- 실패 유형이 제한되어 있다
- 운영을 위한 메타 정보가 포함된다
4단계: 과한 타입을 제거한다
리팩터링을 하다 보면 다음과 같은 유혹이 생깁니다.
// 과한 예시
type ServiceResponse<T, E, M> = {
ok: boolean;
data?: T;
error?: E;
meta?: M;
};
이 타입은 유연해 보이지만, 실제로는 아무 실수도 막아주지 않습니다.
이 시점에서 체크리스트를 다시 적용합니다.
- 실제 실수를 막는가? → 아니다
- 변경 비용이 합리적인가? → 아니다
그래서 이 타입은 제거 대상입니다.
운영 관점에서 달라지는 점
이런 단계적 리팩터링을 거치면, 운영에서도 변화가 생깁니다.
- 로그에서 실패 유형을 바로 분류할 수 있다
- 호출부에서 실패 누락이 줄어든다
- 핫픽스 시 영향 범위를 예측하기 쉬워진다
실무 리팩터링 요약 체크리스트
- any부터 제거했는가
- 반환 구조가 타입에 드러나는가
- 실패가 값으로 표현되는가
- 도메인 타입이 단순하게 유지되는가
- 불필요한 제네릭을 제거했는가
좋은 타입 리팩터링은 한 번에 완성되지 않습니다.
any 제거 → 흐름 노출 → 실패 분리 → 과한 타입 제거 이 순서를 지키면, 기존 코드를 망가뜨리지 않으면서도 타입 안정성을 단계적으로 끌어올릴 수 있습니다.
'개발 > Typescript' 카테고리의 다른 글
| [TYPESCRIPT] 리팩터링 우선순위 예제로 보는 타입 개선 전략: 어디서부터 손대야 효과가 나는가 (0) | 2026.02.01 |
|---|---|
| [TYPESCRIPT] 타입 자동화 적용 한계: 권한·피처 플래그·환경 변수에서 멈춰야 할 지점 (0) | 2026.01.30 |
| [TYPESCRIPT] 문자열 키 자동 생성 전략: Mapped Types와 Conditional Types로 설정 규칙을 고정하는 방법 (0) | 2026.01.29 |
| [TYPESCRIPT] Template Literal Types 활용 전략: 문자열을 타입으로 안전하게 조작하는 방법 (0) | 2026.01.28 |
| [TYPESCRIPT] typescript 판단 기준 요약: 주니어부터 시니어까지 흔들리지 않는 선택 가이드 (0) | 2026.01.27 |
