Rain Lag

1주일 디버그 다이어리 실험: 모든 버그를 재사용 가능한 해결 패턴으로 바꾸기

1주일 동안 디버그 다이어리를 쓰는 실험을 통해, 매번 해결하는 버그를 재사용 가능한 패턴으로 정리해 두고, 시간이 지날수록 디버깅을 더 빠르고 안정적이며 덜 고통스럽게 만드는 개인 지식 베이스를 쌓는 방법을 알아봅니다.

소개

대부분의 개발자는 버그를 방해 요소로 취급합니다. “진짜” 일에서 벗어나게 만드는 짜증 나는 샛길이죠. 로그를 허겁지겁 더 찍고, 설정을 바꾸고, Stack Overflow를 대충 훑어보고, 어떻게든 문제를 고칩니다. 그리고 곧바로 다음 일로 넘어가고, 방금 배운 것들은 금세 잊어버립니다.

이건 큰 낭비입니다.

디버깅은 당장의 문제만 해결하는 일이 아닙니다. 내 코드, 도구, 시스템을 깊이 이해할 수 있는 기회이기도 합니다. 이 이해를 제대로 기록해 두면, 한 번 해결한 버그는 “일회성 전투”가 아니라 다음에 계속 써먹을 수 있는 자산이 됩니다.

이 글에서는 1주일 디버그 다이어리 실험을 통해, 매 버그를 다음과 같이 바꾸는 방법을 다룹니다.

  • 나중에 다시 알아볼 수 있는 문서화된 근본 원인(root cause)
  • 여러 프로젝트에 재사용할 수 있는 해결 패턴(fix pattern)
  • 전체 코드 품질을 조금씩 끌어올리는 의미 있는 개선 포인트

필요한 건 약간의 구조화, 검증된 디버깅 기법들, 그리고 개인용 지식 베이스뿐입니다.


사고방식 전환: “고치고 잊기”에서 “고치고 배우기”로

대부분 우리는 보통 이렇게 디버깅합니다.

  1. 에러가 나타난다(알람, 로그, 사용자 제보 등).
  2. 당황 / 짜증.
  3. 이거저거 막 해본다: print 찍기, 설정 아무렇게나 바꾸기, 테스트 재실행.
  4. 어찌어찌 해결된다.
  5. 넘어간다. 일주일쯤 지나면 대부분 잊는다.

이건 반응형(reactive) 디버깅입니다. 당장의 불은 끄지만, 그 과정에서 크게 똑똑해지진 않습니다.

디버그 다이어리 접근법은 이걸 완전히 뒤집습니다.

  • 모든 버그를 하나의 **사례 연구(case study)**로 본다.
  • 목표는 단순히 에러 메시지를 없애는 것이 아니라, 정확히 무슨 일이, 왜 일어났는지와 시스템이 그걸 어떻게 허용했는지를 이해하는 것이다.
  • 개별 사건이 아니라 패턴을 학습의 기본 단위로 취급한다.

이런 식으로 쌓이면 결과적으로:

  • 해결 시간이 줄어듭니다(비슷한 문제를 예전에 이미 다뤄봤으니까).
  • 같은 실수를 반복하는 일이 줄어듭니다(안티패턴을 초기에 알아봅니다).
  • 내 시스템에 대한 **머릿속 모델(mental model)**이 훨씬 선명해집니다.

1주일 디버그 다이어리 실험 (개요)

앞으로 7일 동안, 사소하지 않은 버그를 만날 때마다 간단한 템플릿에 기록합니다. 습관만 붙으면 버그 하나당 5–10분이면 충분합니다.

실험은 크게 세 부분으로 구성됩니다.

  1. 아무렇게나 찍어보는 게 아니라 구조화된 디버깅 기법을 사용한다.
  2. 각 버그를 일관된 다이어리 형식으로 기록한다.
  3. 그 기록에서 재사용 가능한 해결 패턴을 뽑아서 개인 지식 베이스에 쌓는다.

이제 하나씩 살펴보겠습니다.


1. 구조화된, 검증된 디버깅 기법 사용하기

“찍어보고, 바꿔보고, 운 좋기를 바라는” 식이 아니라, 문제 해결을 더 빠르고 안정적으로 만들어 주는 도구와 방법을 씁니다.

이번 주 동안 특히 의식적으로 써볼 만한 핵심 기법들은 다음과 같습니다.

Git Bisect

git bisect를 사용하면 이분 탐색(binary search)으로 회귀(regression)를 추적할 수 있습니다.

  • **정상으로 동작하던 커밋(좋은 커밋)**과 **문제가 발생한 커밋(나쁜 커밋)**을 지정합니다.
  • 그러면 Git이 중간 지점을 계속 제안하고, 그 시점이 괜찮은지/깨졌는지를 체크해 나가면서 최초로 깨진 커밋을 찾아갑니다.

“diff를 쳐다보며 찍어 맞추기”를, 결정론적인 프로세스로 대체하는 셈입니다.

근본 원인 분석(Root Cause Analysis, RCA)

에러 메시지가 사라졌다고 끝내지 마세요. 스스로에게 물어봅니다.

  • 정확히 무엇이 이 동작을 일으켰나?
  • 왜 테스트 / 모니터링 / 설정이 이 문제를 걸러주지 못했나?
  • 무엇이 있었으면 이 문제가 애초에 발생하지 않았을까?

증상(symptom) 말고 **근본 원인(root cause)**을 써야 합니다. 예를 들어, “53번 줄에서 NPE”는 증상이고, “외부에서 들어온 데이터가 검증 없이 널이 허용되지 않는 필드로 들어갔다”가 근본 원인입니다.

최소 재현 / 축소(Systematic Reduction / Minimization)

실패 케이스를 최대한 작은 예제로 줄여 보세요.

  • 테스트 데이터 줄이기
  • 관련 없는 설정 제거하기
  • 비핵심 코드 경로는 주석 처리하기

이 과정에서 종종 진짜 트리거 조건이 드러납니다. (예: “캐시가 콜드이고, 잡이 병렬로 돌 때만 깨진다.”) 또, 이렇게 줄여 둔 예제는 회귀 테스트로 추가하기도 훨씬 쉽습니다.

계측 & 로깅(Instrumentation & Logging)

아무 곳에나 console.log를 덕지덕지 붙이는 게 아니라, 의도적인 로깅을 합니다.

  • 단순 에러가 아니라 입력값, 주요 의사결정, 경계(boundary) 지점을 기록합니다.
  • Correlation ID / Request ID를 붙여 요청 단위로 추적할 수 있게 합니다.
  • 가능하면 구조화 로그를 사용하고, 검색이 잘 되도록 합니다.

실험 기간 동안 “컴포넌트 X와 Y 사이에서 무슨 일이 일어나는지 안 보인다”는 생각이 들면, 그 사이에 로깅을 추가하고, 그 개선 내용을 다이어리에 함께 기록해 두세요.

타임 트래블 디버깅 / 리플레이(Time-Travel Debugging / Replay)

일부 IDE, 디버거, 혹은 프로덕션 리플레이 도구가 제공하는 기능입니다.

  • 실패 지점에서 뒤로(step back) 거슬러 올라가며 실행 흐름을 추적합니다.
  • 현재 값만 보는 게 아니라 변수 변경 히스토리를 살펴봅니다.

레이스 컨디션이나 플래키(flaky) 테스트를 분석할 때 특히 강력합니다.

이런 기법들을 사용해야 다이어리에 실질적인 내용이 쌓입니다. “대충 이렇게 했더니 되더라”가 아니라, 다시 따라 할 수 있는 구체적인 프로세스를 기록하게 됩니다.


2. 디버그 다이어리 템플릿: 모든 버그를 같은 형식으로 기록하기

Obsidian, Notion, 혹은 그냥 Markdown 파일 등에 템플릿 노트를 하나 만들어 두세요. 이번 주에 다루는 버그마다 이 템플릿을 채우면 됩니다.

간단한 템플릿 예시는 다음과 같습니다.

# 디버그 다이어리 – YYYY-MM-DD – [짧은 버그 제목] ## Context - **Project/Service:** - **Environment:** (dev/stage/prod/local) - **Trigger:** (deployment, feature launch, config change, random, etc.) - **First signal:** (log, alert, user report, failing test) ## Symptoms - **Observed behavior:** - **Expected behavior:** - **Error messages / logs:** - **Reproduction steps:** (as exact as possible) ## Investigation - **Initial hypothesis:** - **Techniques used:** (git bisect, logs, debugger, time-travel, systematic reduction, etc.) - **Key observations:** ## Root Cause - **What actually caused this?** - **Why did the system allow it?** (tests, monitoring, design gaps) ## Fix - **Change made:** - **Why this fixes it:** - **Tests added/updated:** ## Reusable Fix Pattern - **Pattern name:** (e.g., "Null from external API -> validation & defaults") - **When it applies:** - **How to apply it next time:** ## Anti-Pattern / Lesson - **Underlying anti-pattern:** - **Preventative action:** (lint rule, convention, checklist item, test, doc)

마지막 부분, 즉 Reusable Fix PatternAnti-Pattern / Lesson 섹션이 장기적인 가치가 쌓이는 핵심입니다.


3. 재사용 가능한 해결 패턴 개인 지식 베이스 만들기

디버그 다이어리의 각 항목은 말하자면 **원석(raw material)**입니다. 시간이 지나면, 이 원석들이 반복해서 등장하는 문제와 해결책의 카탈로그가 됩니다.

툴은 본인 워크플로에 맞는 걸 쓰면 됩니다.

  • Obsidian: Markdown 파일로 관리, 백링크와 #fix-pattern, #anti-pattern 같은 태그 사용.
  • Notion: “카테고리”, “스택”, “심각도” 같은 속성을 가진 테이블/데이터베이스.
  • Plain Markdown + Git: 리포지토리 안에 debug/ 디렉토리를 두거나, 별도의 노트용 리포를 만들어 버전 관리 & 검색.

개별 버그 → 재사용 가능한 패턴으로 승격시키기

비슷한 유형의 문제가 두 번 이상 반복되는 게 보이면, 해당 버그 노트에서 내용을 추려서 별도의 해결 패턴 노트로 승격시킵니다. 예를 들어:

Fix Pattern: 외부 API 방어적 처리(Defensive Handling of External APIs)

  • 적용 상황:
    • 서드파티 API나 외부 연동으로부터 데이터를 받아올 때
    • 필드가 비어 있거나(null), 누락되었거나, 예상치 못한 형식일 수 있을 때
  • 주요 증상:
    • Null pointer exception
    • JSON 파싱 중 크래시
    • 눈에 잘 안 보이는 데이터 손상(silent data corruption)
  • 표준 해결 방법:
    • 경계(boundary)에서 모든 입력값 검증
    • 선택적(optional) 필드가 비어 있을 때 쓸 기본값(default) 정의
    • 모르는/추가된 필드는 에러 대신 로그만 남기고 허용
    • Contract test나 스키마 검증 추가
  • 사전 예방:
    • 검증 로직을 내장한 공용 ExternalApiClient 래퍼를 도입
    • 깨진(payload가 이상한) mock 데이터를 사용하는 테스트 추가

다음에 비슷한 문제가 생기면 처음부터 다시 고민할 필요가 없습니다. “아, 이거 외부 API 방어적 처리 패턴이네.” 하고 바로 연결할 수 있죠.

반복되는 안티패턴 추적하기

많은 버그는 사실 비슷한 원인 클래스에서 나옵니다.

  • 입력값 검증 누락
  • 스레드 간 공유 가변 상태(shared mutable state)
  • 서비스 간 숨겨진 결합(hidden coupling)
  • 매직 스트링, 여러 코드 경로에 중복된 비즈니스 로직

각 다이어리 항목에 안티패턴 태그를 붙여 두면, 결국 나를 괴롭히는 진짜 공통 원인이 뭔지 보이기 시작합니다.

예를 들면:

  • #anti-pattern: unvalidated external input
  • #anti-pattern: global mutable state
  • #anti-pattern: copy-pasted business logic

패턴이 눈에 보이면, 그때부터 할 수 있는 일들이 많아집니다.

  • Lint rule 추가
  • 체크리스트에 항목 추가
  • 잘못 쓰기 어렵고, 올바르게 쓰기 쉬운 유틸리티 / 추상화 계층 도입

이렇게 디버그 다이어리는 장기적인 기술 부채(technical debt)를 줄이는 도구가 됩니다.


예시로 보는 한 주: 실제로는 이렇게 보입니다

가상의 한 주를 예로 들어 보겠습니다.

  • 버그 1: 프로덕션에서 결제 웹훅이 가끔 실패한다.

    • 근본 원인: 모든 웹훅 payload에 customer_id가 있다고 가정함.
    • 해결 패턴: "외부 API 방어적 처리".
  • 버그 2: CI에서 테스트가 가끔 실패하는 플래키 테스트.

    • 근본 원인: 테스트가 시스템 시계에 의존하고 있고, 잡 시작 시점과 검증 시점 사이의 레이스.
    • 해결 패턴: "시간 의존 로직 → Clock 추상화 주입".
    • 안티패턴: "도메인 로직에서 시스템 시계를 직접 사용".
  • 버그 3: 새 기능 릴리스 이후 API 엔드포인트가 느려짐.

    • 근본 원인: 새로운 리포팅 기능에서 N+1 쿼리가 발생.
    • 해결 패턴: "DB 접근 배치 처리 / 연관 데이터 prefetch".
  • 버그 4: 프론트엔드에서 가끔 오래된(stale) 데이터가 보인다.

    • 근본 원인: 특정 업데이트 경로에서 캐시 무효화(cache invalidation)가 누락됨.
    • 해결 패턴: "중앙 이벤트 기반 캐시 무효화".

일주일이 끝나면, 단순히 4개의 버그를 고친 게 아닙니다.

  • 4개의 상세한 디버그 다이어리
  • 3–5개의 재사용 가능한 해결 패턴
  • 의식적으로 피해야 할 3개의 안티패턴

몇 달만 이렇게 쌓으면, 사실상 나만의 디버깅 플레이북이 생깁니다.


습관으로 만들기 (속도 안 늦추고)

많이들 걱정합니다. “좋은 건 알겠는데, 버그마다 이렇게 글을 쓸 시간이 없다….”

현실적으로 적용할 수 있는 팁은 이렇습니다.

  • 작게 시작하기. 15분 이상 걸린 버그만 기록해도 됩니다.
  • 시간 제한 두기. 다이어리 하나당 5–10분 안에 쓰기. 문장 아니어도 되고, 그냥 불릿 포인트면 충분합니다.
  • 보일러플레이트 자동화. 템플릿/스니펫을 만들어 dd-new 같은 단축어를 치면 기본 구조가 자동으로 들어가게 만드세요.
  • 주 1회 리뷰. 15–20분 정도만 투자해서 새로 추가된 항목을 훑어보고, 반복되는 내용은 해결 패턴으로 승격시킵니다.

지금 약간의 오버헤드를 투자해 두면, 몇 달 뒤의 나는 비슷한 문제를 몇 시간 대신 몇 분 만에 해결하게 됩니다.


마무리

디버깅은 꼭 매번 정신없고, 스트레스 많고, 끝나면 다 잊어버리는 일이 될 필요가 없습니다.

1주일짜리 디버그 다이어리 실험을 통해:

  • 각 버그를 금방 잊히는 해프닝이 아니라, 문서화된 사례 연구로 남길 수 있고
  • Git Bisect, RCA, 최소 재현, 체계적인 로깅, 타임 트래블 디버깅 같은 구조화된 기법으로 디버깅 프로세스를 안정화하고
  • 상황, 증상, 근본 원인, 해결책을 담은 개인 지식 베이스를 구축하며
  • 반복되는 해결 패턴안티패턴을 추출해, 디버깅 시간과 장기적인 기술 부채를 모두 줄일 수 있습니다.

버그를 고치는 일 자체는 이미 하고 있습니다. 디버그 다이어리는 그 노력이 만들어 낸 가치가 사라지지 않게 해 줄 뿐입니다.

딱 1주일만 시도해 보세요. 최소한, 까다로운 몇 가지 문제를 이전보다 훨씬 잘 이해하게 될 것입니다. 잘만 맞는다면, 반응적으로 불 끄는 개발자가 아니라, 문제를 깊이 이해하고 시스템을 근본적으로 개선하는 효과적인 디버거로 성장하는 발판이 될 겁니다.

1주일 디버그 다이어리 실험: 모든 버그를 재사용 가능한 해결 패턴으로 바꾸기 | Rain Lag