버그를 만나면 바로 코드를 고치고 싶어진다. 하지만 무엇이 문제인지 정의하지 않고 고치면 증상만 덮게 된다. 다섯 단계로 문제를 정의하고 좁혀 가면 추측 대신 근거로 원인에 닿는다. 이 틀은 NEXTSTEP 「이펙티브 디버깅」 강의에서 다룬 내용을 정리한 것이다.
1. 문제 정의
지금 실제로 무슨 일이 일어나는지 사실로 적는다. "결제가 안 돼요"가 아니라 "쿠폰을 적용한 주문에서 결제 버튼을 누르면 500 응답이 온다". 에러 메시지는 처음부터 끝까지, 두 번 읽는다.
2. 올바른 동작 정의
원래 어떻게 동작해야 하는지 적는다. 기대가 분명하지 않으면 무엇이 버그인지도 분명하지 않다. 명세나 문서와 다르게 내가 기대하고 있었을 수도 있다.
3. 최소 재현 환경을 만들며 관찰
버그가 일어나는 가장 작은 조건(최소 재현, Minimal Reproduction)을 만든다. 줄여 가는 과정에서 버그가 사라지는 지점이 곧 단서다 → 최소 재현. 재현이 되면 실패하는 테스트로 고정해 둔다.
4. 차이를 만드는 원인 탐색
1과 2의 차이를 만들 수 있는 원인을 거르지 말고 모두 적는다. 한두 가지 그럴듯한 가능성에 매몰되지 않기 위해서다. 잘 되는 경우와 안 되는 경우를 비교한다. 입력, 환경, 버전, 최근 변경 → git bisect로 원인 커밋 찾기
5. 가설 설정과 검증
목록에서 하나를 골라 "X 때문이라면 Y를 하면 결과가 Z일 것이다"라고 가설(Hypothesis)을 세우고 확인한다. 질문은 20분 안에 답할 수 있을 만큼 작게 쪼갠다. 한 번에 하나만 바꾸고, 결과에 따라 목록을 지워 간다. 시도한 것과 결과를 기록해 두면 오래 걸리는 디버깅에서도 길을 잃지 않는다.
팁
- 가정을 믿지 않는다. 확실해 보이는 전제에 어서션을 걸어 확인한다
- 막히면 문제를 남에게 설명하듯 다시 정의해 본다 → 러버덕 디버깅
- 고친 뒤에는 2단계에서 정의한 올바른 동작을 테스트로 남긴다 → 자가 테스트 코드
알고리즘 문제를 풀 때 문제를 먼저 정확히 이해하는 알고리즘 문제 풀이 접근법, 해결책보다 문제를 먼저 보는 문제 공간과 해결 공간와 같은 태도다. 더 많은 전략은 디버깅 포켓 가이드 (개요)에 있다.