tsc 실패는 원인일 수 없다: 기존 오류가 있는 프로젝트에서 문제점을 분리하는 방법
요약
기존에 오류가 있는 TypeScript 프로젝트에서 개발자가 자신의 변경 사항이 문제의 원인인지 분리하여 생각하는 방법을 제시합니다. 단순히 `tsc` 실패만으로 책임을 단정 짓지 않고, '변경 전'과 '변경 후'의 타입 체크 결과를 비교(diff)하여 새로 추가된 에러만을 정확히 파악하는 것이 중요함을 강조합니다.
핵심 포인트
- `tsc` 실패는 사실일 뿐, 변경이 원인이라는 결론은 아님.
- 기존 오류가 있는 경우, 변경 전후의 결과 파일을 비교해야 함.
- `diff` 명령어를 사용하여 새로 추가된 에러만 분리할 수 있음.
- 비교 시에는 TypeScript 버전 및 `tsconfig` 등 실행 조건을 통일해야 함.
TypeScript 프로젝트에서
tsc
을 실행했더니 엄청난 양의 에러가 나왔습니다.
—
기존에 오류가 있던 환경에서, 자신의 변경으로 인한 영향을 분리하고 싶다
'커맨드가 실패했다 = 내 변경이 원인이다'라고 생각해 본 적이 있다
- AI에게 구현이나 검증을 맡길 때, 결과를 어떻게 평가해야 할지 고민하고 싶다
이 글에서는 기존에 타입 에러가 있는 TypeScript 프로젝트를 예로 들어서,
tsc의 실패라는 '사실'과 그 '원인'을 분리하여 생각하는 방법
-
변경 전후의 baseline을 사용하여 새로 추가된 오류를 분리하는 방법
-
변경 파일만 확인할 때의 한계
-
검증 결과로부터 '무엇이 증명되었는지'를 생각하는 시점
을 정리합니다.
이번 배움을 한마디로 표현하자면,
'오류가 있다'와 '내 변경이 오류를 발생시켰다'는 별개입니다.
TypeScript 코드를 수정한 후, 타입 에러가 없는지 확인하기 위해 tsc을 실행했습니다.
npx tsc --noEmit
그런데 결과는 엄청난 양의 에러였습니다.
처음에 이 결과를 보면,
내 변경으로 타입 에러를 발생시킨 건가?
라고 생각하기 쉽습니다.
하지만 프로젝트 전체를 살펴보면, 이번에 변경하지 않은 부분에도 이전부터 존재하던 타입 에러가 있었습니다.
여기서 중요했던 것은,
'tsc이 실패했다'는 사실과 '이번 변경이 원인으로 tsc이 실패했다'는 판단은 별개라는 것입니다.
이 글에서는 기존에 타입 에러가 있는 TypeScript 프로젝트에서, 변경으로 인해 새로운 문제를 발생시키지 않았는지 분리하는 사고방식을 정리합니다.
예를 들어, 설명을 단순화하여, 변경 후의 타입 체크에서 100건의 에러가 확인되었다고 가정해 봅시다.
변경 후: 100건
여기서 알 수 있는 사실은,
현재 코드베이스에 100건의 타입 에러가 있다는 것뿐입니다.
아직,
이번 변경으로 인해 100건의 에러가 발생했다고는 판단할 수 없습니다.
어쩌면,
변경 전: 100건
변경 후: 100건
일 수도 있습니다.
혹은,
변경 전: 100건
변경 후: 103건
일 수도 있습니다.
이 두 가지는 의미가 완전히 다릅니다.
tsc가 실패했다
↓
이번 변경이 원인이다
라고 바로 결론 내리기보다는,
tsc가 실패했다
↓
이것은 사실이다...
라고 나누어 생각할 필요가 있습니다.
이 분리를 할 때 가장 이해하기 쉬운 방법은, 변경 전의 상태를 남겨두는 것입니다.
예를 들어, 변경 전에 다음 커맨드를 실행합니다.
npx tsc --noEmit --pretty false \
> /tmp/tsc-before.txt 2>&1
변경 후에도 같은 조건으로 실행합니다.
npx tsc --noEmit --pretty false \
> /tmp/tsc-after.txt 2>&1
그리고 차이점을 확인합니다.
diff -u /tmp/tsc-before.txt /tmp/tsc-after.txt
참고로, 비교할 때는 TypeScript나 의존성(dependency), tsconfig 같은 실행 조건을 맞추어야 합니다. 조건 자체가 바뀌면 코드 변경 외의 이유로도 진단 결과가 변화할 가능성이 있습니다.
생각은 간단합니다.
변경 전 상태
↓
변경...
기존 에러가 대량으로 남아있더라도,
이번 변경으로 무엇이 바뀌었는지
를 분리하기 쉬워집니다.
정상적인 프로젝트라면,
npx tsc --noEmit
만 성공하는 것만 봐도 충분할 때가 있습니다.
하지만 이미 기존 에러가 있는 경우,
PASS / FAIL
만으로는 이번 변경을 평가할 수 없습니다.
예를 들어,
Before
기존 에러: 100건
After
...
이라면, 프로젝트 전체의 타입 체크는 여전히 실패하고 있습니다.
하지만,
이번 변경으로 인해 새로운 타입 에러가 추가되었다고는 할 수 없습니다.
반대로,
Before
기존 에러: 100건
After
...
이라면, 그 1건을 확인해야 합니다.
또한, 에러 건수만으로는 새로운 에러의 유무를 판단할 수 없습니다. 변경 전후로 건수가 같더라도, 기존 에러가 해소되는 동시에 새로운 에러가 추가되었을 가능성이 있습니다. 따라서 건수뿐만 아니라 진단 내용의 차이점을 확인합니다.
전체 타입 체크 결과가 매우 클 경우, 보조적으로 변경한 파일에 관한 에러를 좁힐 수도 있습니다.
예를 들어, 변경한 TypeScript 파일을 가져옵니다.
git diff --name-only --diff-filter=AM -- '*.ts' '*.tsx'
타입 체크 결과에서 해당 파일과 관련된 출력만 검색할 수도 있습니다.
npx tsc --noEmit --pretty false 2>&1 |
rg -F -f <(
git diff --name-only --diff-filter=AM -- '*.ts' '*.tsx'
...
하지만, 여기에는 주의가 필요합니다.
변경한 파일에 에러가 표시되지 않음 = 이번 변경이 새로운 타입 에러를 전혀 발생시키지 않았음
이라고 단정할 수는 없습니다.
어떤 파일의 타입을 변경한 결과, 그 타입을 이용하는 다른 파일에서 에러가 발생하는 경우도 있기 때문입니다.
변경한 파일 A
↓
타입이 바뀜
...
이 경우, 에러가 표시되는 것은 B입니다.
따라서,
변경 파일만 확인하는 것
은 어디까지나 보조적인 확인일 뿐입니다.
더 중요한 것은,
변경 전과 변경 후의 타입 체크 결과 그 자체를 비교하는
것이라고 생각했습니다.
타입 체크를 하다 보면 다른 문제에 부딪힐 때도 있습니다.
예를 들어,
JavaScript heap out of memory
입니다.
이것은,
TypeScript 코드에 타입 에러가 있다는
것을 의미하는 것이 아닙니다.
타입 분석을 실행하고 있는 Node.js 프로세스가 사용 가능한 힙의 상한에 도달했다는 상태입니다.
사용 가능한 메모리 양을 고려하여, 예를 들어 다음과 같이 힙 상한을 조정하여 재실행할 수 있습니다.
node --max-old-space-size=8192 \
./node_modules/typescript/bin/tsc \
--noEmit \
...
여기서도 중요한 것은,
tsc가 정상 종료되지 않았음
이라는 현상만 보고,
타입 에러가 원인
이라고 단정하지 않는 것입니다.
tsc가 완주하지 못함
↓
왜?
...
으로 분해하여 확인합니다.
이번 같은 상황에서는, 검증 결과의 표현에도 주의가 필요하다고 느꼈습니다.
예를 들어,
타입 체크 OK
이라고 써버리면,
프로젝트 전체의
tsc
가 정상 종료된
것으로 읽힐 수도 있습니다.
실제로는,
프로젝트 전체
→ 기존 타입 에러가 남아 있음
변경 파일
...
이 상태일지도 모릅니다.
그렇다면, 그대로 전달하는 것이 더 정확합니다.
예를 들어,
프로젝트 전체에는 기존 타입 에러가 있습니다. 변경 전후의 타입 체크 결과를 비교하여, 이번 변경으로 인해 새로운 타입 에러가 추가되지 않았음을 확인했습니다.
처럼, 무엇을 확인했는지를 구체적으로 쓰는 것이 좋습니다.
이것은 단순한 말遣(말씀 방식)의 문제가 아닙니다.
검증 결과를 올바르게 전달하기 위해서는,
어떤 방법으로, 어느 범위까지 확인할 수 있었는지를
스스로 이해하고 있어야 합니다.
AI를 사용해서 구현이나 검증을 진행하다 보면,
타입 체크를 실행한 결과, 기존 에러가 있었습니다.
혹은,
변경한 파일에는 에러가 없습니다.
와 같은 보고를 받곤 합니다.
여기서도, 그 문장만으로 판단하지 않도록 하고 싶었습니다.
확인하고 싶은 것은,
언제 실행했는가?
↓
변경 전에도 같은 에러가 있었나?
...
라는 부분입니다.
AI가 커맨드를 실행할 수 있어도,
그 결과가 무엇을 증명하는 것인지를
판단할 필요는 남아 있습니다.
이번 경험에서 인상 깊었던 것은, 특정 TypeScript 커맨드 그 자체보다는 문제의 보는 방식이었습니다.
처음 눈에 보이는 것은,
tsc가 실패했다
라는 현상입니다.
거기서부터,
내 변경이 잘못되었다
라고 바로 원인을 결정하는 것이 아니라,
사실
↓
tsc가 실패했다
...
로 나누어 생각합니다.
이것은 TypeScript에 국한된 이야기가 아닙니다.
테스트가 실패했을 때도, build가 실패했을 때도, 화면에서 불량이 일어났을 때도,
눈앞의 현상과, 그 원인이라고 생각하는 것은 다릅니다.
기존에 타입 에러가 있는 프로젝트에서 tsc를 실행하면,
대량의 에러
이 표시되는 경우가 있습니다.
하지만 여기서 알 수 있는 것은,
현재 에러가 존재한다는 사실까지입니다.
이번 변경이 원인인지 아닌지를 판단하려면,
변경 전 상태를 확인한다
↓
변경 후 상태를 확인한다
...
가 필요합니다.
제가 이번에 남게 된 판단 기준은,
'에러가 있다'와 '내 변경이 에러를 발생시켰다'를 분리하는 것입니다.
그리고 AI가 구현이나 검증을 해준다고 하더라도,
tsc가 실패했다는 결과뿐만 아니라,
그 결과가 이번 변경에 대해 무엇을 증명하고 있는지를까지 생각합니다.
TypeScript의 타입 체크를 소재로 삼았지만, 최종적으로는 디버깅이나 검증 전반에 사용할 수 있는 사고방식입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기