Chapter 6: Debugging & Verification
Debugging은 추측이 아닙니다. 체계적이고 증거 기반의 프로세스입니다. Superpowers는 가장 일반적인 실패 패턴 — 무작위로 fix를 시도하는 것, 불완전한 검증, 너무 이른 완료 선언 — 을 제거하는 구조적인 debugging 접근 방식을 강제합니다.
4단계 Debugging 프로세스
모든 버그 조사는 반드시 이 네 단계를 순서대로 따라야 합니다. 단계를 건너뛰는 것은 허용되지 않습니다.
Phase 1: 근본 원인 조사
fix 코드를 한 줄이라도 작성하기 전에, 문제를 완전히 이해해야 합니다.
- 에러를 주의 깊게 읽으세요. 전체 에러 메시지, stack trace, 그리고 주변 context를 복사하세요. 요약하지 마세요 — 정확한 텍스트로 작업하세요.
- 실패를 재현하세요. 실패한 test를 실행하거나 버그를 수동으로 트리거하세요. 재현할 수 없는 버그는 신뢰할 수 있게 fix할 수 없습니다.
- 최근 변경 사항을 확인하세요.
git log --oneline -20과git diff HEAD~5를 실행하여 최근에 무엇이 변경되었는지 파악하세요. 대부분의 버그는 최근 변경으로 인해 도입됩니다. - 데이터 흐름을 추적하세요. 데이터의 진입점부터 모든 변환을 거쳐 예상값과 달라지는 지점을 찾을 때까지 따라가세요.
규칙: 조사 단계를 완료하기 전에 가설을 세우지 마세요. 이론보다 증거가 먼저입니다.
Phase 2: 패턴 분석
버그를 재현했다면, 패턴을 찾으세요.
- 작동하는 예제를 찾으세요. codebase에서 올바르게 작동하는 유사한 code path를 찾으세요. 작동하는 코드와 고장난 코드의 차이가 버그일 가능성이 높습니다.
- 차이점을 비교하세요. 체계적으로 diff를 확인하세요 — 타입, nullability, async 처리, 에러 전파, 부작용.
- 타이밍을 확인하세요. 많은 버그는 race condition, 초기화 순서 문제, 또는 async/await 실수입니다. 스스로에게 물어보세요: 동기적으로는 작동하지만 비동기적으로 실패하는가? 처음에는 작동하지만 retry 시 실패하는가?
Phase 3: 가설과 검증
증거를 수집했다면, 이제 가설을 세우고 검증할 수 있습니다.
- 가설을 명시적으로 서술하세요. 적어두세요: "X 때문에 버그가 발생한다고 생각합니다. 증거는 Y입니다."
- 한 번에 하나의 변수만 테스트하세요. 두 가지를 동시에 변경하지 마세요. fix가 작동하지 않는다면, 어느 변경이 원인인지 알 수 없게 됩니다.
- 결과를 문서화하세요. 무엇을 테스트했는지, 무엇을 예상했는지, 실제로 무슨 일이 일어났는지 기록하세요. 이 로그는 동일한 잘못된 가설을 다시 테스트하는 것을 방지합니다.
Phase 4: 구현
이제 근본 원인을 알고 있습니다. fix를 제대로 구현하세요.
- 먼저 실패하는 test를 만드세요. 정확한 버그를 재현하는 test를 작성하세요. fix 전에 실패하고 fix 후에 통과해야 합니다.
- 최소한의 fix를 구현하세요. 식별된 근본 원인을 fix하는 데 필요한 것만 변경하세요. refactor하지 마세요, 관련 없는 코드를 개선하지 마세요.
- regression이 없는지 확인하세요. 전체 test suite를 실행하세요. fix는 이전에 통과하던 것을 깨뜨려서는 안 됩니다.
3번 실패 규칙
3번 이상 fix를 시도했는데도 버그가 지속된다면, 즉시 멈추세요.
세 번의 실패한 fix 시도는 더 열심히 하라는 신호가 아닙니다 — 진단 신호입니다. 증상을 치료하고 있지, 근본 원인을 치료하고 있지 않다는 의미입니다. 이 시점에서:
- 문제는 거의 확실히 아키텍처 문제이지 코드 레벨의 버그가 아닙니다
- 계속해서 fix를 시도하면 codebase가 더 나빠집니다
- 에스컬레이션해야 합니다: 계획을 작성하고, 영향받는 모듈을 재설계하거나, 다른 시각을 구하세요
이 규칙은 기술 부채를 축적하고 codebase를 유지 불가능하게 만드는 "fix 위에 fix 위에 fix"의 일반적인 실패 패턴을 방지합니다.
Single Fix 규칙
여러 가지를 동시에 fix하지 마세요.
버그를 발견했을 때, 근처에 다른 문제들도 있을 수 있습니다. 한꺼번에 모두 fix하려는 충동을 억제하세요. 왜냐하면:
- 어느 변경이 원래 버그를 fix했는지 파악할 수 없습니다
- 새로운 실패를 특정 변경에 귀속시킬 수 없습니다
- test coverage가 불분명해집니다
- Code review가 불가능해집니다
한 가지를 fix하세요. 검증하세요. commit하세요. 그런 다음 다음 문제로 이동하세요.
완료 전 Verification
이것은 Superpowers의 Iron Law 중 하나입니다:
"최신 검증 증거 없이는 완료 선언을 할 수 없습니다"
방금 검증을 실행하고 출력 결과가 앞에 있지 않은 한, "fixed", "done", "working" 또는 그에 상응하는 어떤 표현도 할 수 없습니다. 이것은 선택 사항이 아닙니다.
5단계 Verification Gate
모든 완료 선언은 이 gate를 통과해야 합니다:
| 단계 | 행동 | 의미 |
|---|---|---|
| IDENTIFY | 검증하는 구체적인 것을 명명하세요 | "로그인 flow의 모든 test가 통과하는지 확인합니다" |
| RUN | 실제 검증 명령을 실행하세요 | npm test -- --testPathPattern=auth |
| READ | 완전한 출력을 읽으세요 | 훑어보지 마세요. 모든 줄을 읽으세요. |
| VERIFY | 출력이 성공 기준과 일치하는지 확인하세요 | 모든 test 통과, 경고 없음, 예상 동작 확인 |
| CLAIM | 이제 작업이 완료됐다고 선언하세요 | "Test가 통과됩니다. Fix가 검증되었습니다." |
어느 단계라도 실패하면, debugging 프로세스로 돌아갑니다. 완료를 선언하지 마세요.
주의해야 할 Red Flags
다음 표현과 행동은 검증이 건너뛰어졌음을 나타냅니다:
| Red Flag | 의미하는 것 |
|---|---|
| "이것이 작동해야 합니다" | 검증이 수행되지 않았습니다 |
| "아마 괜찮을 겁니다" | 증거 없는 가정 |
| "fix된 것 같습니다" | 객관적 검증이 아닌 주관적 믿음 |
| "test가 통과해야 합니다" | Test가 실행되지 않았습니다 |
| 재실행 없이 이전 보고서를 신뢰하는 것 | 오래된 데이터 — 상황은 변합니다 |
| 출력을 보여주는 대신 요약하는 것 | 선택적 읽기 또는 hallucination 가능성 |
| 코드 대신 test를 fix하는 것 | 버그를 해결하는 것이 아닌 덮어두는 것 |
이러한 표현 중 하나를 말하려 한다면, 멈추세요. 검증을 실행하세요. 그런 다음 출력이 실제로 무엇을 말하는지 보고하세요.
종합 정리
올바른 debugging 세션은 다음과 같습니다:
1. 버그 보고: "사용자 로그인이 401로 실패함"
2. Phase 1: 재현 → git log 확인 → auth middleware 추적
3. Phase 2: 작동하는 endpoint 찾기 → 헤더 비교 → 파악: JWT 토큰에 하나의 code path에서 "Bearer " prefix가 누락됨
4. Phase 3: 가설: "모바일 클라이언트는 raw 토큰을 보내고, 데스크톱은 'Bearer TOKEN'을 보냄. Backend는 'Bearer'를 기대함."
Test: raw 토큰 수동 전송 → 401. "Bearer" prefix로 전송 → 200. 확인됨.
5. Phase 4: raw 토큰 입력에 대한 test 작성 → auth middleware에 정규화 구현 → 전체 suite 실행 → 847개 test 통과, 0개 실패
6. Verification gate: IDENTIFY "auth tests" → RUN `npm test` → READ 출력 → VERIFY 0개 실패 → CLAIM "Fixed."
체계적인 debugging은 단기적으로 더 느리지만 프로젝트 수명 전반에 걸쳐 극적으로 빠릅니다. 추측은 신뢰할 수 있는 소프트웨어의 적입니다.