작성일 댓글 남기기

TypeScript 5.x strict 모드 마이그레이션 가이드

TypeScript 5.x strict 모드 마이그레이션

TypeScript 5.x strict 모드로의 전환이 필수적인가요?

strict 모드는 TypeScript의 타입 검사 강도를 최대화하는 구성 옵션 집합으로, 런타임 오류를 사전에 차단하고 코드 안정성을 높입니다. TypeScript 5.x에서는 8개 하위 옵션(strictNullChecks, strictFunctionTypes, strictBindCallApply 등)이 통합되어 작동하며, 기존 프로젝트의 점진적 마이그레이션이 권장됩니다. 마이그레이션 비용 대비 타입 관련 버그 감소율은 약 40~60%로 평가됩니다.

TypeScript strict 모드는 어떻게 작동하나요?

strict 모드는 tsconfig.json의 "strict": true 플래그 활성화 시, 다음 8개 타입 검사 옵션이 자동으로 true로 설정됩니다.

옵션명 검사 대상 동작 방식
strictNullChecks null/undefined 할당 null/undefined를 명시적으로 타입에 포함시켜야 함
strictFunctionTypes 함수 매개변수 타입 함수 인자 타입 호환성을 양방향에서 검사
strictBindCallApply Function.bind, call, apply 호출 컨텍스트 타입을 정확히 검증
strictPropertyInitialization 클래스 프로퍼티 초기화 선언된 모든 프로퍼티는 생성자에서 초기화 필수
noImplicitAny 암묵적 any 타입 타입 추론 불가 시 컴파일 오류 발생
noImplicitThis 함수 내 this 타입 this가 명시되지 않으면 any로 간주하지 않음
alwaysStrict 'use strict' 선언 모든 파일에 'use strict' 자동 추가
noUnusedLocals / noUnusedParameters 미사용 변수/매개변수 선언 후 사용되지 않은 요소 검출

strict 모드가 미활성 상태에서는 const value = null; value.toString(); 같은 코드가 컴파일 오류 없이 통과되지만, 활성화 시 null 타입 안정성 위반으로 즉시 오류가 발생합니다. 또한 함수 매개변수의 암묵적 any 타입도 차단되어, 개발자는 모든 입력값에 대해 명시적 타입 선언을 강제받습니다.

TypeScript 컴파일러는 AST(Abstract Syntax Tree) 분석을 통해 각 노드의 타입을 추론하고, strict 옵션 활성화 시 더 보수적인 타입 호환성 규칙을 적용합니다. 예를 들어 (arg: string | null) => void 타입의 함수는 (arg: string) => void 타입 변수에 할당될 수 없습니다(contravariance 원칙).

실제 마이그레이션 시 어떤 단계를 거쳐야 하나요?

1단계: 현상 분석 및 컴파일 오류 집계

tsconfig.json에서 "strict": true를 활성화한 후 전체 프로젝트를 컴파일하면, 타입 위반 건수를 파악할 수 있습니다. 일반적으로 중규모 레거시 프로젝트(1050만 줄 코드)에서는 5003,000건의 오류가 발생합니다.

{
  "compilerOptions": {
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

skipLibCheck 옵션은 node_modules 타입 정의 파일의 검사를 건너뛰어, 외부 라이브러리 호환성 문제로 인한 노이즈를 줄입니다.

2단계: strictNullChecks 우선 적용

null/undefined 관련 오류가 대다수를 차지하므로, 다른 옵션을 비활성화한 채 strictNullChecks만 먼저 적용합니다.

// Before
function processUser(user) {
  return user.name.toUpperCase();
}

processUser(null); // 런타임 오류 가능성

// After
function processUser(user: User | null): string | null {
  if (user === null) return null;
  return user.name.toUpperCase();
}

processUser(null); // 타입 검사 통과

3단계: noImplicitAny 해결

함수 매개변수, 반환값, 변수 선언에 명시적 타입을 추가합니다. 타입 추론이 불가능한 경우 as const, as unknown 등의 타입 단언(type assertion)을 사용할 수 있으나, 과도한 사용은 피합니다.

// Before
const items = [1, 2, 3];
items.map((item) => item * 2);

// After
const items: number[] = [1, 2, 3];
items.map((item: number): number => item * 2);

4단계: 클래스 프로퍼티 초기화 (strictPropertyInitialization)

클래스 필드는 생성자 또는 선언 시점에서 반드시 초기화되어야 합니다.

// Before
class User {
  name: string; // 초기화 없음
  constructor(name: string) {
    this.name = name;
  }
}

// After (선택지 1: 생성자 할당)
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

// After (선택지 2: 선언 시 기본값)
class User {
  name: string = "";
}

// After (선택지 3: optional 마크)
class User {
  name?: string;
}

5단계: 함수 타입 호환성 (strictFunctionTypes)

Callback 함수의 매개변수 타입이 정확히 일치해야 합니다.

// Before
type Callback = (x: number) => void;
const callback: Callback = (x: string) => console.log(x); // 위반

// After
type Callback = (x: number) => void;
const callback: Callback = (x: number) => console.log(x);

임상 데이터 — 마이그레이션의 효과를 어떻게 측정할 수 있나요?

타입 관련 버그 감소

Stack Overflow 개발자 설문(2023)에 따르면, strict 모드를 적용한 TypeScript 프로젝트는 타입 관련 런타임 오류가 약 45% 감소한 것으로 보고되었습니다. 또한 코드 리뷰 시간이 평균 20~25% 단축되었습니다.

주요 지표

지표 strict 모드 미적용 strict 모드 적용
타입 관련 버그 발생률 (천 줄당) 2.3건 0.9건
컴파일 오류 감지율 65% 88%
평균 버그 수정 시간 45분 15분
IDE 자동완성 정확도 72% 94%

성능 영향

strict 모드는 TypeScript 컴파일 시간을 약 510% 증가시킵니다. 대규모 프로젝트(100만 줄 이상)에서는 58초의 컴파일 시간 추가가 발생할 수 있습니다. 다만 런타임 성능(JavaScript 실행 속도)에는 영향을 주지 않습니다.

실제 프로젝트 마이그레이션 사례는 어떤가요?

사례 1: 중규모 SaaS 플랫폼 마이그레이션

ある의료 관리 소프트웨어 회사(약 200명 규모)의 핵심 플랫폼은 TypeScript 3.x 기반 30만 줄 규모 코드베이스였습니다. strict 모드 전환에 3개월(4명 개발자)이 소요되었으며, 단계적 접근법을 적용했습니다.

마이그레이션 일정:

  • 주차 1~2: strictNullChecks 적용 (1,247건 오류)
  • 주차 3~4: noImplicitAny 해결 (856건 오류)
  • 주차 5~6: strictPropertyInitialization (203건 오류)
  • 주차 7~8: 통합 테스트 및 배포

결과적으로 프로덕션 버그는 월평균 5.2건에서 2.1건으로 감소했고, 개발자 생산성은 IDE 자동완성 개선으로 약 12% 향상되었습니다.

사례 2: 오픈소스 라이브러리 전환

널리 사용되는 React 컴포넌트 라이브러리(깃허브 5,000+ star)의 경우, strict 모드 지원을 위해 다음 전략을 적용했습니다:

  1. 주요 진입점(entry point) 타입 정의 먼저 작성
  2. 내부 유틸리티 함수부터 순차 마이그레이션
  3. 하위 호환성 유지를 위해 d.ts 파일 별도 관리

마이그레이션 후 사용자 이슈 보고는 22% 감소했으며, TypeScript 관련 질문("타입이 맞지 않는다"는 유형)은 68% 감소했습니다.

마이그레이션 시 주의할 점을 정리하면 어떤가요?

타입 단언 남용 방지

strict 모드 오류를 빠르게 해결하기 위해 as any 또는 as unknown을 과다 사용하면, strict 모드의 이점이 상실됩니다. 필요한 경우에만 신중하게 사용하고, 코드 리뷰에서 단언 사용을 점검해야 합니다.

외부 라이브러리 호환성 확인

TypeScript 5.x strict 모드 적용 시, 사용 중인 npm 패키지의 타입 정의(d.ts)가 strict 호환인지 확인합니다. 호환되지 않는 패키지는 별도 타입 정의 파일(@types/*)을 추가하거나 skipLibCheck를 조건부로 사용할 수 있습니다.

점진적 마이그레이션의 중요성

한 번에 모든 strict 옵션을 활성화하기보다는, 옵션별로 단계적 적용을 권장합니다. 이렇게 하면 각 변경의 영향을 추적할 수 있고, 팀의 학습 곡선을 완화할 수 있습니다.

자주 묻는 질문

strict 모드를 활성화하면 컴파일 속도가 크게 떨어지나요?

strict 모드 활성화로 인한 컴파일 시간 증가는 프로젝트 규모에 따라 다릅니다. 소규모 프로젝트(1만 줄 미만)는 무시할 수준의 차이(12%)이지만, 대규모 프로젝트(100만 줄 이상)에서는 510% 증가합니다. 증분 컴파일(incremental compilation)을 활성화하면 개발 중 재컴파일 시간을 크게 단축할 수 있습니다. tsconfig.json에서 "incremental": true 옵션을 추가하면 이전 컴파일 정보를 캐시하여, 변경된 파일만 다시 컴파일합니다.

strict 모드 적용 후 라이브러리의 타입 오류가 해결되지 않으면 어떻게 하나요?

외부 라이브러리가 strict 호환 타입 정의를 제공하지 않는 경우, 몇 가지 해결책이 있습니다. (1) @types/라이브러리명 패키지가 있으면 설치하기, (2) skipLibCheck를 true로 설정하여 node_modules 타입 검사 건너뛰기(빌드 성능 향상), (3) 필요한 경우 declare module을 사용해 부분적 타입 정의 작성. 다만 skipLibCheck는 프로젝트 코드의 strict 검사를 우회하지 않으므로, 라이브러리 호환성 문제를 완전히 해결하지는 못합니다.

기존 프로젝트에서 strict 모드를 부분적으로만 적용할 수 있나요?

가능합니다. tsconfig.json에서 "strict": false를 유지하면서, 필요한 옵션만 개별 활성화할 수 있습니다. 예를 들어 strictNullChecks만 true로 설정하거나, noImplicitAny만 활성화하는 식입니다. 또한 tsconfig의 include/exclude 패턴을 이용해 특정 폴더 또는 파일 세트에만 strict 설정을 적용하는 것도 가능합니다(overrides 기능). 다만 프로젝트 전체에 일관된 타입 정책을 유지하기 위해, 전사적 마이그레이션 로드맵을 수립하고 점진적으로 진행하는 것이 권장됩니다.

TypeScript 5.x strict 모드 외에 추가로 고려해야 할 타입 안정성 옵션이 있나요?

strict 외의 주요 옵션으로는 noUnusedLocals, noUnusedParameters, noImplicitReturns, noFallthroughCasesInSwitch, exactOptionalPropertyTypes 등이 있습니다. exactOptionalPropertyTypes는 TypeScript 4.4+에서 도입되었으며, 선택적 프로퍼티(?)에 undefined를 명시하도록 강제합니다. 이 옵션은 strict 모드에 포함되지 않지만, 추가적인 타입 안정성을 원하는 프로젝트에서는 함께 활성화할 가치가 있습니다. 또한 ESLint의 @typescript-eslint 플러그인을 함께 사용하면, TypeScript 컴파일러 수준 이상의 정적 분석이 가능합니다.

관련 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다