한 가지 질문으로 끝내는 디버깅 습관: 잘못된 버그를 쫓지 않게 해주는 작은 멘탈 체크
디버깅에 들어가기 전에 단 하나의 명확한 질문을 던지는 습관만으로, 엉뚱한 버그를 몇 시간씩 쫓는 일을 줄이고, 겉증상이 아닌 진짜 근본 원인을 체계적으로 찾을 수 있다.
한 가지 질문으로 끝내는 디버깅 습관: 잘못된 버그를 쫓지 않게 해주는 작은 멘탈 체크
실패하는 테스트나 깨진 기능을 바라보고 있다. 에디터를 열고 로그를 여기저기 뿌려 보고, 디버거를 켜 보거나, AI 어시스턴트에게 도움을 요청한다. 10분, 20분, 40분이 지나면 함수 호출과 스택 트레이스 속을 헤엄치고 있는데, 문득 깨닫는다. 애초에 완전히 잘못된 곳을 뒤지고 있었다는 걸.
문제는 그 서비스에 있지 않았다. 그 함수에도 있지 않았다. 그 파일에도 없었다.
잘못된 버그를 쫓고 있었던 거다.
이런 일이 생기는 이유는 도구나 실력이 부족해서가 아니다. 아주 작은 멘탈 단계 하나를 건너뛰었기 때문이다. 바로, 지금 내가 믿고 있는 현재 가설이 정말 말이 되는지, 잠깐 멈춰서 묻지 않았다는 것.
이 글은 디버깅 전에 (그리고 디버깅 도중에도) 자신에게 던지는 단 하나의 질문이라는 작은 습관을 심는 방법에 대한 이야기다. 이 질문만 잘 써도 쓸데없는 시간을 크게 줄이고, 진짜 근본 원인에 더 빨리 다가갈 수 있다.
핵심 습관: 단 하나의 작은 질문
로그를 추가하거나, 브레이크포인트를 걸거나, AI에게 도움을 요청하기 전에, 몇 초만 멈추고 이렇게 물어보자.
“지금 이 위치에 버그가 있다고 생각하는 근거가 뭐지? 다른 데가 아니라 여기라고 확신할 수 있는 이유가 있을까?”
딱 이거 하나다.
이 짧은 질문은 ‘추측 기반 디버깅’을 막아주는 안전장치다. 첫 느낌을 그대로 믿고 파기 시작하는 대신, 다음을 강제로 하도록 만든다.
- 내가 세운 현재 가설을 명시적으로 표현하고
- 그게 내가 이미 알고 있는 사실들과 맞는지 확인하고
- 정말 여기에서 더 깊이 파볼 가치가 있는지 판단하게 만든다
이 질문은 스타일에 맞게 이렇게 바꿔 물어도 된다.
- “내 가설이 맞다면, 또 어떤 현상이 함께 보여야 하지?”
- “이 가설이 틀렸다는 걸 빠르게 보여줄 수 있는 건 뭐지?”
- “지금 이 증상을 합리적으로 만들 수 있는 컴포넌트는 어디 어디일까?”
문장 자체는 중요하지 않다. 중요한 건, 아무 생각 없이 계속 파고들던 흐름을 잠깐 끊고, 내 머릿속 모델을 의도적으로 점검하는 데 있다.
왜 개발자들은 엉뚱한 버그에 시간을 낭비할까
대부분의 디버깅 낭비는 아주 단순한 패턴에서 시작된다.
- 뭔가가 고장났다.
- 즉석에서, 대충 떠오르는 가설을 세운다. “아, 캐시 문제겠지.”
- 직감이 가리키는 곳부터 파기 시작한다.
- 시작 가설이 맞는지 의심해보지도 않은 채, 로그를 더 붙이고 브레이크포인트를 더 깊게 넣으면서 디테일만 계속 추가한다.
이 과정은 이상하게도 편안하다. 뭔가 계속 하고 있으니, 마치 진전을 이루는 것처럼 느껴진다. 코드를 타이핑하고, 값들을 들여다보고, 코드 구석구석을 탐험하고 있으니까. 하지만 노력은 곧 진전이 아니다. 출발점 가설이 틀렸다면, 그저 잘못된 터널을 예쁘게 장식하고 있을 뿐이다.
문제의 뿌리는 검증되지 않은 가정에 있다.
- “아마 프론트엔드 문제일 거야.” (왜냐면 방금 거길 고쳤으니까.)
- “이 함수일 가능성이 커.” (예전에 비슷한 버그가 여기서 났으니까.)
- “아마 데이터 레이스겠지.” (동시성은 모두를 불안하게 만드니까.)
한 가지 질문 습관은 이런 걸 잘라낸다. 스스로에게 묻게 만든다.
- “내가 왜 이렇게 믿고 있지?”
- “이게 맞다면, 어떤 모습이 보여야 하지?”
명확한 멘탈 모델: 첫 번째 디버깅 도구
이 질문이 제대로 작동하려면, 시스템이 어떻게 동작하는지에 대한 어느 정도의 멘탈 모델이 필요하다. 완벽할 필요는 없다. 다만 다음 질문에 답할 만큼은 구체적이어야 한다.
“지금 보이는 이 버그를 그럴듯하게 만들 수 있는 시스템의 부분은 어디 어디지?”
쓸 만한 멘탈 모델이라면 대략 이런 것들을 포함한다.
- 핵심 컴포넌트 (서비스, 모듈, 레이어)
- 데이터 플로우 (입력이 어떻게 출력으로 변하는지)
- 컨트롤 플로우 (어떤 코드가 언제, 무엇을 호출하는지)
- 경계와 계약 (각 컴포넌트가 무엇을 보장하고 무엇은 보장하지 않는지)
이 그림이 머릿속에 있으면, 관심 없는 영역을 한꺼번에 잘라낼 수 있다.
- 버그가 네트워크 호출 전에 이미 나타난다면, 원격 API는 범인일 가능성이 낮다.
- 출력이 항상 같은 방식으로 일관되게 틀리다면, 플래키한 네트워크보다는 결정적 변환 로직 버그일 가능성이 크다.
- 로그를 보면 함수에 들어가기 직전 데이터는 맞고, 함수에서 나온 데이터가 틀리다면, 검색해야 할 범위는 크게 줄어든다.
이 멘탈 모델 덕분에 이렇게 말할 수 있다. “이 증상이 나타나려면, 이 경로 어딘가에서 문제가 생겨야 한다.” 이제 한 가지 질문 습관이 훨씬 효과적으로 일할 수 있다.
근본 원인 vs 증상: 정확한 목표를 노려라
디버깅의 핵심 목표는 진짜 근본 원인(root cause) 을 찾는 것이다. 눈앞의 증상만 대충 가리는 게 아니다.
- 증상: “사용자 장바구니 총합이 0으로 나온다.”
- 임시 땜질: “total이 0이면 프론트엔드에서 다시 계산한다.”
- 근본 원인: “특정 쿠폰 조합에서 할인 계산이 언더플로우되고, 그 값이 0으로 클램핑된다.”
한 가지 질문 체크는, 지금 세운 가설이 겉증상이 아니라 근본 원인까지 설명하는지 의도적으로 점검하게 해줘야 한다.
이렇게 물어보자.
- “이 가설이 맞다면, 지금까지 관찰된 모든 증상을 설명할 수 있나?”
- “이걸 고치면, 같은 버그가 다른 형태로 다시 나타나는 걸 막을 수 있을까?”
대답이 “글쎄…”, “일부만 설명하는데…” 정도라면, 아마 증상만 다루고 있을 가능성이 크다.
조금만 더 깊은 원인을 의식하게 만들면, 취약한 워크어라운드를 계속 쌓는 일을 피할 수 있고, 나중의 나와 팀 동료들에게 더 안정적인 시스템을 남기게 된다. 금 간 벽에 페인트만 덧칠하는 대신, 구조를 손보는 셈이다.
디버깅을 ‘실험의 연속’으로 바꾸기
효과적인 디버깅은 무작정 파헤치는 일이 아니다. 가설에 기반한 일련의 실험에 가깝다.
각 실험은 이런 식의 가설에서 출발한다.
“페이지네이션 버그는 프론트엔드 렌더링이 아니라 백엔드 쿼리에서 시작된 것 같다.”
여기서 한 가지 질문 습관은 자연스럽게 두 가지 후속 질문을 호출한다.
-
“내 가설이 맞다면, 또 어떤 현상이 보여야 하지?”
- 예: 프론트로 가기 전에 로그에 이미 중복된 row가 찍혀야 한다.
- 혹은: API 응답의
totalPages값이 이미 잘못돼 있어야 한다.
-
“이 가설이 틀렸다는 걸 가장 빠르게 보여줄 수 있는 건 뭐지?”
- 예: 한 번의 API 호출만으로, 백엔드 데이터는 맞고 프론트 렌더링만 틀렸다는 걸 보여줄 수 있다면, 가설은 바로 깨진다.
이제 로그와 브레이크포인트는 ‘대충 여기저기 찍어보는 도구’가 아니라, 실험을 검증하기 위한 정밀 도구가 된다.
- API 응답 원본을 한 번만 로그로 남긴다. 렌더링 트리 전부를 뒤집어쓰지 않는다.
- 데이터가 백엔드에서 프론트로 넘어오는 경계 지점에만 브레이크포인트를 건다.
실험 결과가 가설을 깨뜨린다면, 그건 실패가 아니다. 훌륭한 진전이다. 한 경로를 제거했고, 머릿속 시스템 모델이 더 날카로워졌다.
도구를 제대로 쓰는 법: 가설 중심 디버깅
디버거, 프로파일러, 로그, 트레이싱, AI 어시스턴트 같은 도구들은 강력하다. 하지만 이 도구들은 일종의 증폭기다. 이미 하고 있는 프로세스를 증폭시킬 뿐이다.
- 아무 생각 없이 쓰면, 무작정 헤매는 과정을 증폭한다.
- 명확한 가설과 함께 쓰면, 정확한 학습과 인사이트를 증폭한다.
디버거나 AI를 켜기 전에, 스스로에게 이 세 가지를 물어보자.
-
지금 내 가설은 정확히 뭐지?
“버그는 외부 API 응답을 내부 모델로 매핑하는 과정에 있다.” -
이 가설을 확인하거나 깨뜨릴 구체적인 관찰은 뭐지?
“매핑 함수 직후의 모델을 찍어 봤을 때 이미 잘못되어 있으면 매핑이 문제고, 그 시점엔 맞는데 나중에 틀어지면 그 이후 로직이 문제다.” -
이걸 테스트해볼 수 있는, 코드 상의 가장 좁은 지점은 어디지?
“매핑 함수가 값을 반환한 직후.”
이제 디버거 세션에는 분명한 미션이 생긴다. 그 시점의 객체 모양을 확인하는 것이다. 더 이상 “일단 한 줄씩 따라가다 보면 수상한 게 보이겠지…”라는 마음이 아니라, 구체적인 가설을 검증하거나 폐기하는 과정이 된다.
AI를 쓸 때도 마찬가지다.
- 나쁜 질문: “내 코드 뭐가 잘못됐나요?”
- 더 나은 질문: “API 응답은 맞는 것 같은데 UI에 잘못된 값이 나오니, 매핑 로직에 버그가 있다고 생각합니다. 이 가설을 빨리 확인하거나 깨뜨릴 수 있는 테스트나 관찰 포인트를 제안해 줄 수 있나요?”
한 가지 질문 습관은, 기본적으로 당신을 이 ‘더 나은 모드’로 밀어 넣는다.
이 습관을 몸에 익히는 방법
이 정도로 작은 습관이 진짜 힘을 가지려면, 매번 사용해야 한다. 자동화되도록 만드는 몇 가지 방법은 다음과 같다.
-
디버깅 전 체크리스트를 만들자.
- “지금 정확한 증상은 뭐지?”
- “지금 내 가설은 뭐지?”
- “이 위치에 버그가 있다고 생각하는 근거는 뭐지? 다른 데가 아니라 왜 여기지?”
-
에디터나 이슈 트래커에 직접 적어두자.
- 디버깅 노트 맨 위에: Hypothesis(가설): …
- 그 아래에: Evidence(근거): 여기라고 생각하는 이유: …
-
페어 프로그래밍이나 코드 리뷰 때 소리 내어 말하자.
- “우리가 여기 로그를 붙이는 이유는, 이 지점에서 데이터가 잘못된다고 생각하기 때문이야. 그런데 애초에 이 위치가 연관됐다는 증거가 뭐가 있지?”
-
타임아웃 트리거로 쓰자.
- 15분 동안 진전이 없으면, 멈추고 이렇게 물어본다.
“내 가설이 틀렸다면, 그 사실을 내가 알아챌 수 있는 방법이 있나? 없다면 그게 문제 아닌가?”
- 15분 동안 진전이 없으면, 멈추고 이렇게 물어본다.
시간이 지나면 이게 자동화된다. 디버깅을 시작하면서, **“내가 지금 뭘 테스트하려는 거지?”**를 말로 못 하면 뭔가 꺼림칙하게 느껴지기 시작한다.
짧은 예시 하나
다음과 같은 버그 리포트를 받았다고 해보자.
“여러 개의 할인(쿠폰 등)을 함께 적용할 때, 가끔씩 청구서(invoce) 총합이 잘못 계산됩니다.”
첫 생각은 이럴 수 있다. “아, 새로 만든 discount 서비스 문제겠네.”
하지만 곧바로 그 내부로 뛰어들기 전에, 습관을 적용해 보자.
- 가설: discount 서비스가 잘못된 total을 반환한다.
- 질문: “이 위치에 버그가 있다고 생각하는 근거가 뭐지? 다른 데가 아니라 왜 discount 서비스지?”
지금 상태에서? 아무 근거도 없다.
그래서 빠른 실험을 하나 설계한다.
- 라인 아이템 원본과 discount 서비스 응답을 그대로 로그로 남긴다.
- 사람이 손으로 계산한 ‘정답’과 비교해 본다.
로그를 보니 discount 서비스가 반환한 discount total은 정확하다면, 초기 가설은 사망이다. 이제 버그 후보는 이렇게 바뀐다.
- 세금 계산 로직
- 반올림/올림/버림 로직
- 통화(currency) 변환 로직
가설을 업데이트하고, 다음 작은 실험을 설계하고, 또 반복한다.
이렇게 하면 discount 서비스 안에서 한 시간 허우적거리는 대신, 몇 분 만에 그 가설을 죽이고 다음 후보로 넘어갈 수 있다.
결론: 더 깊이 파지 말고, 더 날카롭게 생각하라
디버깅이 괴로운 진짜 이유는 도구 부족이 아니다. 너무 오랫동안 엉뚱한 곳을 파기 때문이다.
아주 단순하고 반복 가능한 습관 하나로 이 상황을 바꿀 수 있다.
디버깅을 시작하기 전과 진행 중에, 항상 이렇게 물어봐라. “이 위치에 버그가 있다고 생각하는 근거가 뭐지? 다른 데가 아니라 왜 여기지?”
여기에 시스템에 대한 명확한 멘탈 모델과 근본 원인에 대한 집착이 더해지면, 로그도, 브레이크포인트도, AI 도구도 훨씬 더 강력하게 변한다.
버그의 위치를 처음부터 정확히 짚어내는 일은 앞으로도 드물 것이다. 틀리는 건 어쩔 수 없다. 하지만 한 가지는 바꿀 수 있다.
같은 잘못된 가설을 몇 시간씩 붙잡고 있는 일은 크게 줄일 수 있다.
다음에 디버거를 켜려고 손이 갈 때, 2초만 멈추고 이 질문을 해 보자.
그 작은 체크 한 번이, 그날 오후 남은 시간을 통째로 아껴줄지 모른다.