디버깅 마음가짐 — 가설과 검증의 사이클
디버깅 마음가짐 — 가설과 검증의 사이클
새 기능을 만드는 시간보다 이미 만든 코드의 문제를 잡는 시간이 더 길다는 보고는 흔합니다. 디버깅은 별도의 기술이라기보다 마음가짐에 가까움. 무엇을 가정하고 있고, 무엇을 검증하지 않았는지를 정직하게 봄. 이 글은 디버깅의 일반적 흐름 · 가설-검증 사이클 · print 와 디버거의 자리 · 이분 탐색과 러버덕 · 스택 트레이스 읽기.
1. 디버깅에 대한 이야기
"디버그 (debug)" 라는 단어 자체는 1947 년 Grace Hopper 의 일화에서 자주 인용. 하버드 마크 II 컴퓨터의 릴레이 사이에 끼어 있던 나방을 떼어 내고 로그에 "first actual case of bug being found" 라 적었다는 이야기. "버그" 라는 표현은 그보다 앞선 19 세기 토머스 에디슨의 편지에도 등장하지만, 컴퓨터 산업에서 굳어진 계기로 이 일화가 자주.
디버깅 고전 텍스트:
- Brian Kernighan & Rob Pike, The Practice of Programming (1999) — 5 장 디버깅.
- David J. Agans, Debugging (2002) — 9 가지 규칙.
- Andy Hunt & Dave Thomas, The Pragmatic Programmer (1999) — 디버깅 챕터, 러버덕의 출처.
2. 가설 → 검증 사이클
체계적 디버깅의 골자:
- 현상 관찰 — 무엇이 일어났는가. 정확히.
- 가설 세우기 — "이게 원인일 것 같다."
- 검증 실험 설계 — 그 가설이 맞다면 어떤 실험에서 어떤 결과가 나올지.
- 실행 — 실험.
- 결과 비교 — 가설이 맞았는가, 틀렸는가.
- 맞았으면 수정, 틀렸으면 새 가설로 1 번부터.
핵심은 5 번. 가설이 틀렸다는 결과가 나오면 그 가설을 깔끔히 버림. 사람은 자기 가설에 끌려 결과를 왜곡 해석하는 경향 (확증 편향). 디버깅은 이 편향과의 싸움이라 부르는 글이 많습니다.
3. print 디버깅 vs 디버거
| 도구 | 강점 | 약점 |
|---|---|---|
console.log · print |
어디서나, 즉시. 시계열로 흐름을 봄 | 흩어진 로그가 누적, 정리 부담 |
| 디버거 (브레이크포인트) | 변수 · 콜스택 풍부, 단계별 실행 | 비동기 · 다중 스레드 자리는 여전히 어려움 |
둘은 대체 관계가 아닙니다. 익숙해지면 자연스럽게 둘을 섞어 씀. 입문 시기에는 print 만으로도 충분히 멀리.
print 의 작은 요령:
console.log("[user load] before fetch", { userId });
console.log("[user load] response", response.status, await response.clone().text());
- 어떤 자리에서 찍는지 태그.
- 객체는 통째로. 자기가 의심한 한 필드만 찍지 않음.
- 진단 후 로그는 지움. 유지가 필요하면 정식 로거 (winston · pino · logback) 로.
4. 이분 탐색 (bisect)
큰 문제 영역을 절반으로 자르면서 좁혀 들어가는 법:
시도 1: 전체 코드 (200줄) → 에러
시도 2: 앞 100줄 주석 처리 → 에러 사라짐 ⇒ 원인은 앞 100줄
시도 3: 앞 50줄만 살림 → 에러 사라짐 ⇒ 원인은 50~100줄
시도 4: 앞 75줄만 살림 → 에러 ⇒ 원인은 50~75줄
... 8~10 번 안에 한 줄 또는 한 함수까지 좁힘
이 패턴을 git 으로도 — git bisect:
git bisect start
git bisect bad # 현재 커밋이 깨졌음을 표시
git bisect good v1.4.0 # 정상이었던 과거 태그
# git 이 중간 커밋으로 이동시켜 줌
# 동작 확인 후 git bisect good 또는 git bisect bad 반복
git bisect reset # 끝나면 원래 자리로
수십 ~ 수백 커밋 사이에서 회귀를 일으킨 한 커밋을 로그(2) 안에 찾음.
5. 러버덕 디버깅
자기 코드를 다른 사람 (또는 책상 위 인형) 에게 한 줄씩 설명해 본다는 기법. The Pragmatic Programmer 1999 년 판에 정리됐고, 그 전부터 유닉스 문화의 일화로 떠돌았음.
설명하다 보면 자신이 가정한 자리와 실제 코드가 어긋나는 자리가 본인 입에서 튀어나옴. 그 자리가 보통 버그.
LLM 시대에는 챗봇이 좋은 러버덕 역할. 답을 기대하지 말고 설명만으로도 효과.
6. 스택 트레이스 읽는 법
에러가 발생한 함수의 호출 사슬:
TypeError: Cannot read properties of null (reading 'name')
at renderUser (src/views/user.tsx:42:18)
at processChild (node_modules/react-dom/.../react-dom.js:13987:14)
at processBlock (node_modules/react-dom/.../react-dom.js:14123:9)
...
자주 보는 자리:
- 에러 종류 —
TypeError·RangeError·ReferenceError·SyntaxError. 의미가 다름. - 메시지 — 가장 큰 단서.
- 첫 자기 코드 줄 — 라이브러리 내부보다 자기 파일이 우선 의심.
- 줄 번호 — 정확한 위치.
소스 맵이 살아 있으면 minified JS 도 원본 위치로 표시.
7. 일반적 함정 점검표
본격적으로 깊이 파기 전에 통과시키면 좋은 점검:
- 정말로 같은 코드를 실행하고 있는가? 빌드 캐시 · 서비스 워커가 옛 버전을 내주고 있지 않은가?
- 정말로 같은 환경인가?
.env가 안 읽힌 건 아닌가? - 입력 데이터가 정말로 가정한 그것인가? 한 번 찍어 봄.
- 비동기 순서가 가정대로인가? Promise 가 아직 안 풀린 자리에서 값 사용?
- 여러 곳에서 같은 자원에 동시에 쓰고 있지 않은가?
8. 디버거의 갈래
- 언어 내장 — Node 는
--inspect· Python 은pdb/pudb· Java 는 JDWP · Go 는 Delve. - IDE 통합 — VS Code · IntelliJ 가 거의 모든 언어 지원.
- 브라우저 DevTools — Sources 탭의 디버거.
- 시간 여행 디버깅 — rr (Linux) · Replay.io. 거꾸로도 실행.
- 로깅 기반 — 운영 환경처럼 디버거 못 쓰는 자리. structured logging + tracing.
9. 작은 디버깅 흐름
증상: 폼 제출 시 가끔만 서버에 도착 안 함
1) 가설 1: 클라이언트 검증이 막고 있다
실험: 검증 함수 시작에 console.log → 항상 통과 → 가설 폐기
2) 가설 2: 네트워크 요청이 실제로 안 나간다
실험: DevTools Network 탭 → 가끔 아예 안 보임 → 가설 유지
3) 가설 3: 버튼이 form 바깥에 있어 더블클릭 시 두 번째가 onSubmit 을 못 잡는다
실험: HTML 구조 확인 → 버튼이 form 자식이고 정상 → 가설 폐기
4) 가설 4: 첫 클릭의 비동기 작업이 끝나기 전 두 번째 클릭이 onSubmit 을 막는다
실험: 이중 클릭 방지 disable 추가 → 재현 안 됨 → 가설 유지
→ 수정 채택
10. 자주 걸리는 자리
"이 자리는 절대 아닐 거야" — 의심 안 한 자리에 답이 있음. 검증 없이 배제 금지.
여러 곳을 동시에 바꾸기 — 무엇이 효과를 냈는지 모름. 한 번에 한 곳.
장시간 같은 자리에 매달림 — 30 분 막히면 일단 자리에서 일어남. 산책 · 잠 · 다음날 아침에 답이 나오는 일이 흔함.
로그를 안 남기고 디버깅 종료 — 같은 자리에 다시 막히면 처음부터.
재현 안 되는 버그를 "환경 탓" 으로 덮음 — 시간 부족할 때만 임시. 가능하면 재현 환경을 만들어 다시.
에러 메시지 안 읽기 — 디버깅 시간의 절반은 메시지를 차분히 읽으면 사라짐.
하고픈 말
디버깅은 별도 기술이 아니라 마음가짐. 가설 → 검증 → 결과 비교 → 새 가설의 사이클이 핵심. 한 번에 한 곳을 바꾸고, 30 분 막히면 자리에서 일어나고, 에러 메시지를 차분히 읽기. print + 디버거 + 이분 탐색 + 러버덕 + 스택 트레이스 다섯 도구가 함께 있을 때 가장 빠른 자리.
Next
- (learning 끝)
The Practice of Programming (Kernighan & Pike, 1999) · Debugging (David J. Agans, 2002) · The Pragmatic Programmer · Chrome DevTools Sources panel · Python pdb 문서 · git bisect 문서 · The First Computer Bug · rubberduckdebugging.com · How to Report Bugs Effectively (Simon Tatham) 를 참고합니다.