DetectiveWork
주니어 개발자들을 위한 패턴 언어 - 가설과 증거를 통해 버그의 실체에 다가가는 법
Contents
The Story 1: The Blind Guesser vs. The Logic Detective (Programming)
두 명의 개발자, 민수와 하나가 '로그인이 간헐적으로 실패하는 버그'를 조사하고 있다.
민수의 수사 (The Blind Guesser): 민수는 코드를 보자마자 추측하기 시작했다. "음, 세션 타임아웃 때문인가? 일단 타임아웃 시간을 늘려보자." (코드 수정) "여전히 안 되네? 그럼 DB 연결 문제인가? 재시도 로직을 넣어보자." (코드 수정) 민수는 원인을 모른 채 코드를 여기저기 고쳤다. 1시간 후, 버그는 여전히 나타났고 코드는 더 지저분해졌다. 민수는 자신이 무엇을 바꿨는지조차 헷갈리기 시작했다.
하나의 수사 (The Logic Detective): 하나는 키보드에서 손을 떼고 관찰부터 시작했다.
현상 관찰: "실패할 때 공통점이 있나? 아, 특정 브라우저에서만 나타나네."
가설 형성: "쿠키 설정이 해당 브라우저와 충돌하는 것 아닐까?"
증거 수집: 하나는 코드를 고치는 대신 로그를 추가하여 쿠키 값을 확인했다.
가설 검증: "로그를 보니 쿠키 크기가 제한을 넘어가고 있어. 이게 원인이네."
하나는 단 한 줄의 코드 수정으로 문제를 완벽히 해결했다. 그녀는 버그를 고친 것이 아니라, 버그의 정체를 검거했다.
The Story 2: The Lost Keys (Ordinary Life)
민수와 하나는 외출 직전, 집 안에서 자동차 열쇠가 사라진 것을 발견했다.
민수의 수사 (The Messy Search): 민수는 당황해서 집안을 뒤집기 시작했다. 거실 소파 쿠션을 들어내고, 주방 서랍을 열고, 안방 침대 밑을 기어 다녔다. 30분 동안 온 집안을 엉망으로 만들었지만 열쇠는 보이지 않았다. "누가 훔쳐간 거 아냐?" 민수는 근거 없는 추측을 하며 점점 화가 났다.
하나의 수사 (The Evidence Hunt): 하나는 자리에 서서 잠시 생각을 정리했다. "마지막으로 열쇠를 본 게 언제지?" (기억 소환) "아까 시장 가방을 들고 들어올 때 손에 있었어." 하나는 현관문에서부터 주방 식탁까지 자신의 동선을 천천히 따라갔다. "가방을 식탁에 내려놓았고, 그때 전화가 와서 외투를 벗었지." 하나는 외투 주머니를 확인했다. 열쇠가 거기 있었다. 하나는 집안을 어지럽히지 않고도 2분 만에 열쇠를 찾아냈다. 증거를 기반으로 사고하면 진실에 이르는 길이 짧아짐을 하나는 알고 있었다.
Context
시스템에 버그가 발생했거나, 코드가 예상과 다르게 동작한다. PresentMoment에서 실제 벌어지는 일을 보고 있지만, 아직 그 이유를 모른다.
일상적인 상황:
- "이걸 고치면 되겠지?"라는 생각으로 코드를 무작정 수정한다.
- 원인을 모르니 "다시 실행해 보세요"라고 말하며 기적을 바란다.
- 버그가 고쳐져도 "왜 고쳐졌는지" 설명하지 못한다.
- 디버깅 과정이 체계적이지 않고 운에 의존한다.
당신은 지금 어둠 속에서 휘두르는 칼처럼 코딩하고 있다.
Problem
원인을 이해하지 못한 채 시도하는 무작위 수정은 상황을 악화시키고 시간을 낭비하게 만든다.
부작용의 누적: 운 좋게 버그가 사라진 것처럼 보여도, 근본 원인이 남아있어 나중에 더 큰 사고로 이어진다.
학습 기회 상실: 시스템이 어떻게 동작하는지 배울 기회를 놓친다.
혼란의 증가: 여러 번의 추측성 수정이 겹치면, 나중에 진짜 원인을 찾아도 이전 수정 사항들과 얽혀 해결하기 어려워진다.
Solution
프로그래머가 아닌 "탐정"의 마음으로 수사하라. 가설을 세우고, 증거를 찾고, 논리적으로 범인을 지목하라.
셜록 홈즈처럼, 불가능한 것들을 제외하고 남은 것이 아무리 믿기 힘들지라도 그것이 진실이다.
Principle 1: Separate Observation from Inference (관찰과 추론 분리)
내가 '본 것'과 '생각한 것'을 명확히 구분하라.
관찰: "로그에 500 에러가 찍혔다." (사실)
추론: "서버가 죽은 것 같다." (가설)
추론을 사실로 믿는 순간 수사는 미궁에 빠진다. 항상 "내가 이걸 정말로 보았는가?"라고 자문하라.
Principle 2: Form Small Hypotheses (작은 가설 세우기)
거창한 원인을 찾으려 하지 말고, 검증 가능한 작은 가설부터 시작하라.
- "이 함수에 들어오는 값이 null일 것이다."
- "이 조건문이 실행되지 않았을 것이다."
TinyExperiment를 사용하여 이 가설들을 하나씩 빠르게 확인하라.
Principle 3: Collect Evidence (증거 수집)
코드를 바꾸기 전에 먼저 정보를 얻어라.
로그 출력: 변수의 값을 확인하라.
디버거 활용: 실행 흐름을 한 단계씩 따라가라.
데이터 확인: DB에 저장된 실제 데이터를 확인하라.
증거 없는 주장은 기각되어야 한다.
Principle 4: Binary Search for the Bug (범위 좁히기)
버그가 어디 있는지 모를 때는 범위를 반씩 줄여나가라.
- 프로그램의 중간 지점에 로그를 찍어본다.
- 앞부분이 정상이라면 뒤쪽 절반을 수사한다.
- 이 과정을 반복하면 수만 줄의 코드 중에서도 범인을 단 몇 단계 만에 찾을 수 있다.
Real Examples
Example 1: The Vanishing Data
데이터가 DB에 저장되지 않는 문제.
(가설 1) UI에서 데이터를 안 보낸다. -> 브라우저 네트워크 탭 확인 (정상, 기각)
(가설 2) 서버 API에서 데이터를 못 받는다. -> 컨트롤러 첫 줄에 로그 (정상, 기각)
(가설 3) 비즈니스 로직 중간에 유실된다. -> 로직 단계별 로그 (유실 지점 발견!)
- (범인 검거) 특정 조건문이 데이터를 조용히 버리고 있었음.
Example 2: Science over Magic
"분명히 고쳤는데 왜 안 되지?"
(가설) 내가 고친 코드가 반영되지 않았다. -> 빌드 아티팩트 확인
- (증거) 브라우저가 옛날 JS 파일을 캐싱하고 있음.
- (해결) 캐시 무효화 후 정상 작동. 마법은 없다. 오직 증거만 있을 뿐.
Common Pitfalls
"Shotgun Debugging" (산탄총 디버깅)
여기저기 조금씩 다 고쳐보고 하나라도 걸리길 바라는 것.
- 가장 비효율적인 방식이다. 설령 고쳐져도 "왜" 고쳐졌는지 모르기 때문에 똑같은 버그를 또 만든다.
Ignoring the Obvious (명백한 증거 무시하기)
에러 메시지에 "File Not Found"라고 적혀 있는데, 네트워크 설정을 고치고 있는 경우.
- 시스템은 항상 당신에게 말을 걸고 있다. 메시지를 꼼꼼히 읽어라.
Confirmation Bias (확증 편향)
자신의 첫 번째 추측이 맞다는 것을 증명하기 위해 유리한 증거만 보는 것.
- 좋은 탐정은 자신의 가설이 틀렸음을 증명하려고 애쓴다.
Connection to Other Patterns
RootHunting - 범인을 찾았다면, 그가 왜 범행을 저질렀는지 배경(근본 원인)까지 파악하십시오. 심화
PresentMoment - 지금 실제로 일어나고 있는 일에 집중하는 것이 수사의 시작입니다. 기반
TinyExperiment - 가설을 검증하기 위한 가장 빠른 방법입니다. 도구
SafetyNet - 수사가 끝나고 범인을 잡았다면, 다시는 도망가지 못하게 테스트라는 감옥에 가두십시오. 마무리
Signs of Success
- 버그를 잡았을 때 "아, 그렇구나!"라는 깨달음이 온다.
- 팀원들에게 버그의 원인과 해결 과정을 논리적으로 설명할 수 있다.
- 디버깅 중에 코드를 수정하는 횟수가 급격히 줄어든다.
- 복잡한 버그일수록 침착해지고 수사 과정을 즐기게 된다.
The Ultimate Insight
디버깅은 코드를 고치는 작업이 아니라, 당신의 "이해"를 교정하는 작업이다.
버그가 발생했다는 것은 당신이 시스템에 대해 가진 모델이 틀렸다는 뜻입니다. 탐정이 되어 증거를 수집하고 당신의 모델을 수정하십시오. 올바른 모델을 가지게 되면, 해결책은 눈앞에 저절로 나타납니다.
CategoryPatternLanguage CategoryProgramming CategoryDebugging CategoryProblemSolving