Skip to content

디버깅의 예술

서문

코드를 다 작성했는데 실행 중 오류가 발생했다 — 그 다음엔? 많은 초보자가 이 단계에서 막혀 화면만 멍하니 쳐다봅니다. 디버깅(Debug)은 프로그래밍에서 가장 핵심적인 기술 중 하나로, 코드를 작성하는 것 자체보다 더 중요합니다. 왜냐하면 코드 작성은 개발 시간의 30%에 불과하고, 나머지 70%는 문제 이해, 버그 위치 파악, 수정 검증에 사용되기 때문입니다.

이 글에서 무엇을 배우게 될까요?

이 장을 마치면 다음을 얻게 됩니다:

  • 디버깅 사고방식: 체계적인 문제 파악 방법을 확립하여 더 이상 "찍어서 맞추기"를 하지 않게 됩니다
  • 오류 읽기 능력: 오류 메시지를 이해하고 오류 스택에서 빠르게 문제를 파악합니다
  • 자주 쓰는 디버깅 방법: 이분법, 고무오리, 최소 재현 등 고전적인 디버깅 기법 습득
  • 도구 사용 능력: 중단점 디버깅, 로그 디버깅, 네트워크 디버깅 등 도구의 사용 시나리오 이해
  • AI 보조 디버깅: AI를 사용해 디버깅 과정을 가속화하되 AI에 의존하지 않기
내용핵심 개념
1장오류 메시지 읽기오류 유형, 스택 트레이스
2장고전 디버깅 방법이분법, 고무오리, 최소 재현
3장디버깅 도구 상자중단점, 로그, 네트워크 패킷 캡처
4장AI 시대의 디버깅AI 보조 + 수동 판단
5장디버깅 마인드셋과 습관방어적 프로그래밍, 디버그 로그

0. 전경도: 디버깅은 과학적 방법이다

디버깅은 "운에 맡기는 것"이 아니라 엄격한 과학적 과정입니다. 물리학자가 실험을 수행하는 방법론은 디버깅에도 완전히 적용됩니다:

  1. 현상 관찰: 프로그램에 무슨 문제가 있는가? 어떤 오류가 발생했는가?
  2. 가설 세우기: 어떤 원인으로 발생했을 가능성이 있는가?
  3. 실험 설계: 이 가설을 어떻게 검증할 것인가?
  4. 결론 검증: 가설이 맞으면 수정하고, 틀리면 다른 가설을 세운다

디버깅의 황금 법칙

  • 먼저 재현하고, 그 다음에 수정한다: 안정적으로 재현할 수 없는 버그는 수정해도 정말로 수정된 것인지 알 수 없다
  • 한 번에 하나의 변수만 변경한다: 여러 곳을 동시에 변경하면 어느 변경이 문제를 해결했는지 모른다
  • 직관이 아닌 증거를 믿는다: "여기가 문제일 리 없다"고 생각하는 곳이 바로 문제인 경우가 많다
  • 최근에 무엇을 변경했는가?: 80%의 버그는 최근 변경에서 도입된 것이다

1. 오류 메시지 읽기: 오류는 적이 아니라 단서다

초보자가 가장 많이 하는 실수: 오류를 보자마자 당황해서 바로 끄거나 무시합니다. 사실, 오류 메시지는 프로그램이 어디서 문제가 생겼는지 알려주는 것입니다 — 가장 좋은 친구입니다.

1.1 오류의 세 가지 유형

유형언제 나타나는가예시심각도
문법 오류코드가 실행되기도 전에 오류 발생괄호 누락, 키워드 오타수정 가장 쉬움
런타임 오류코드가 특정 줄까지 실행되다가 충돌존재하지 않는 변수 접근, 0으로 나누기중간 난이도
논리 오류코드는 실행되지만 결과가 틀림계산 공식 오류, 조건 판단 반대발견 가장 어려움

1.2 오류 스택을 어떻게 읽는가

JavaScript를 예로 들어, 전형적인 오류 메시지:

TypeError: Cannot read properties of undefined (reading 'name')
    at getUserName (app.js:15:23)
    at handleClick (app.js:42:10)
    at HTMLButtonElement.<anonymous> (app.js:58:5)

위에서부터 아래로 읽기:

  1. 첫 번째 줄: 오류 유형 + 오류 설명 → TypeError, undefinedname 속성을 읽으려고 시도
  2. 두 번째 줄: 오류가 발생한 함수와 위치 → getUserName 함수, app.js 15번째 줄 23번째 열
  3. 이후 줄들: 호출 체인 → 누가 이 함수를 호출했는가? handleClick → 버튼 클릭 이벤트

스택 읽기 요령

위에서부터 원인을 찾고, 아래에서부터 출발점을 찾는다. 첫 번째 줄은 "무슨 오류가 발생했는지" 알려주고, 마지막 줄은 "어디서 시작되었는지" 알려줍니다.

1.3 자주 나오는 오류 유형 빠른 참조

오류 이름의미일반적인 원인
SyntaxError문법 오류괄호 불일치, 쉼표 누락
TypeError타입 오류undefined/null에 대해 연산 수행
ReferenceError참조 오류선언되지 않은 변수 사용
RangeError범위 오류배열 인덱스 초과, 재귀가 너무 깊음
NetworkError네트워크 오류API 요청 실패, CORS 문제
404 Not Found리소스 없음URL 오류, 파일 삭제됨
500 Internal Server Error서버 내부 오류백엔드 코드 충돌

1.4 Python 오류 메시지 비교

Python의 스택은 JavaScript와 반대입니다 — 아래에서부터 위로 읽습니다:

python
Traceback (most recent call last):
  File "main.py", line 10, in <module>
    result = calculate(data)
  File "main.py", line 5, in calculate
    return data["price"] * data["quantity"]
KeyError: 'quantity'

마지막 줄이 오류 원인: KeyError: 'quantity', 딕셔너리에 quantity 키가 없습니다.

다른 언어, 같은 접근 방식

어떤 언어든 오류 메시지에는 세 가지 핵심 정보가 포함됩니다: 무엇이 잘못되었는지(오류 유형), 어디서 잘못되었는지(파일과 줄 번호), 왜 잘못되었는지(오류 설명). 이 세 가지 정보를 추출하는 방법을 배우면 어떤 언어의 오류 메시지든 읽을 수 있습니다.


2. 고전 디버깅 방법: 선인들의 지혜

이 방법들은 어떤 도구도 필요하지 않고, 여러분의 두뇌만 있으면 됩니다. 모든 고급 디버깅 기법의 기초가 됩니다.

2.1 이분법 디버깅

핵심 아이디어: 문제 범위를 절반으로 줄이고, 다시 절반으로 줄여 근원을 찾습니다.

시나리오: 코드가 긴데 어느 부분에서 문제가 발생했는지 모름.

단계:

  1. 코드 중간에 console.log(또는 print) 추가
  2. 중간점 이전에 이미 오류가 발생 → 문제는 상반부에 있음
  3. 중간점 이후에 오류가 발생 → 문제는 하반부에 있음
  4. 오류가 발생한 절반에 대해 위 과정 반복
100줄 코드에 버그 발생
    ↓ 50번째 줄에 log 추가
문제는 50-100번째 줄에 있음
    ↓ 75번째 줄에 log 추가
문제는 50-75번째 줄에 있음
    ↓ 62번째 줄에 log 추가
문제는 60-62번째 줄에서 발견!

이분법의 위력

100줄 코드에서 최대 7번(log₂100 ≈ 7)만으로 특정 줄을 파악할 수 있습니다. 1000줄도 10번이면 충분합니다.

2.2 고무오리 디버깅법

핵심 아이디어: 문제를 한 줄씩 다른 사람(또는 고무오리)에게 "설명"하다 보면 스스로 문제를 발견하게 됩니다.

왜 효과적인가? "코드 작성"과 "코드 설명"은 뇌의 다른 영역을 사용하기 때문입니다. 각 단계의 논리를 언어로 설명해야 할 때, "맞을 것이라 생각했던" 가정들이 드러납니다.

실천 방법:

  1. 문제가 있는 코드 열기
  2. 한 줄씩 설명: "이 줄은 무엇을 하나요? 왜 이렇게 하나요?"
  3. "음, 여기는 ... 잠깐만요"라고 말하는 순간, 버그가 바로 거기 있을 가능성이 높습니다

2.3 최소 재현

핵심 아이디어: 복잡한 문제를 최소화하여 버그를 유발할 수 있는 최소한의 코드만 남깁니다.

왜 중요한가?

  • 복잡한 시스템에서 버그는 다른 코드에 의해 "가려져" 있을 수 있습니다
  • 최소 재현은 방해 요소를 배제하여 문제를 한눈에 파악할 수 있게 합니다
  • 다른 사람에게 도움을 요청할 때도 편리합니다 — 아무도 500줄 코드를 보고 싶어 하지 않습니다

단계:

  1. 새 빈 파일 만들기
  2. 문제와 관련된 코드만 복사
  3. 한 줄을 지울 때마다 버그가 사라지는지 확인하며 점진적으로 삭제
  4. 남은 것이 버그의 근원

2.4 회귀법 (Git Bisect)

핵심 아이디어: 코드가 "이전에는 정상이었는데 지금은 고장 났다면", 어느 커밋에서 문제가 도입되었는지 찾습니다.

bash
# Git에 내장된 이진 탐색 도구
git bisect start
git bisect bad          # 현재 버전에 버그가 있다고 표시
git bisect good abc123  # 정상이었던 이전 버전 표시
# Git이 자동으로 중간 커밋으로 전환하며, 테스트 후 good 또는 bad를 알려줌
# 몇 번 반복하면 버그를 도입한 커밋을 찾을 수 있음

디버깅 방법 선택 가이드

상황추천 방법
어느 코드 구간에서 오류가 나는지 모름이분법
논리는 맞는 것 같은데 결과가 틀림고무오리
복잡한 시스템의 버그최소 재현
"이전까지 멀쩡했는데 갑자기 고장 남"회귀법 / Git Bisect

3. 디버깅 도구 상자: 올바른 도구를 쓰면事半功倍

방법론이 기초지만, 좋은 도구는 디버깅 효율을 배로 높여줍니다.

3.1 console.log / print: 가장 소박하지만 가장 실용적

적용 시나리오: 변수 값 빠르게 확인, 코드가 어디까지 실행되었는지 확인.

javascript
// JavaScript
console.log('함수가 호출되었습니다. 매개변수:', data)
console.log('계산 결과:', result)
console.table(arrayData)  // 배열/객체를 테이블 형태로 표시
python
# Python
print(f"현재 값: {value}")
print(f"타입: {type(data)}")  # 데이터 타입 확인

고급 기법:

메서드용도
console.log()일반 출력
console.warn()노란색 경고, 대량 로그에서 쉽게 찾기
console.error()빨간색 오류
console.table()배열과 객체를 테이블로 표시
console.time() / console.timeEnd()코드 실행 시간 측정
console.trace()호출 스택 출력

3.2 중단점 디버깅: 한 줄씩 실행하며 각 단계 확인

적용 시나리오: 논리가 복잡하여 코드 실행 과정을 단계별로 추적해야 함.

브라우저에서 (Chrome DevTools):

  1. 개발자 도구 열기 (F12) → Sources 패널
  2. 소스 코드 파일 찾기, 줄 번호 클릭하여 중단점 설정
  3. 관련 작업 실행, 코드가 중단점에서 일시 중지
  4. 제어 버튼으로 단계별 실행:
    • 계속 (F8): 다음 중단점까지 실행
    • Step Over (F10): 현재 줄 실행, 함수 내부로 들어가지 않음
    • Step Into (F11): 함수 내부로 들어감
    • Step Out (Shift+F11): 현재 함수에서 빠져나옴

VS Code에서:

  1. 줄 번호 왼쪽 클릭하여 중단점 설정 (빨간 점)
  2. F5 눌러 디버깅 시작
  3. "변수" 패널에서 모든 변수의 현재 값 확인
  4. "조사식" 패널에 관심 있는 표현식 추가

중단점 vs console.log

console.log는 빠른 검증에 적합하고, 사용 후 삭제합니다. 중단점 디버깅은 복잡한 논리 분석에 적합합니다. 둘은 대체 관계가 아니라 보완 관계입니다.

3.3 네트워크 디버깅: 프론트엔드와 백엔드 사이의 문제

적용 시나리오: 페이지 표시가 틀리지만 프론트엔드 문제인지 백엔드가 반환한 데이터 문제인지 불확실.

Chrome DevTools → Network 패널:

확인 내용발견할 수 있는 문제
상태 코드404(주소 오류), 500(서버 충돌), 403(권한 없음)
요청 매개변수프론트엔드가 보낸 데이터가 맞는지
응답 데이터백엔드가 반환한 데이터 형식이 맞는지
요청 시간어떤 API가 너무 느려 페이지를 지연시키는지
요청 헤더Token 포함 여부, Content-Type이 맞는지

디버깅 요령: 먼저 상태 코드를 보고, 다음으로 요청 매개변수를 확인하고, 마지막으로 응답 데이터를 확인합니다.

3.4 디버깅 도구 선택 빠른 참조

문제 유형추천 도구
변수 값이 틀림console.log / 중단점
논리 실행 순서가 틀림중단점 디버깅
API 요청 실패Network 패널
페이지 스타일이 틀림Elements 패널 (CSS 확인)
성능 문제Performance 패널 / console.time
메모리 누수Memory 패널

4. AI 시대의 디버깅: AI를 조수로 활용하기

AI 도구(ChatGPT, Claude, Cursor 등)는 디버깅 과정을 크게 가속할 수 있지만, 사용 방법을 알아야 합니다.

4.1 AI가 잘하는 것과 못하는 것

AI가 잘함AI가 못함
오류 메시지의 의미 설명비즈니스 논리 이해
일반적인 문제의 해결책 제공어떤 해결책이 프로젝트에 가장 적합한지 판단
디버깅 코드 스니펫 생성특정 환경에서만 발생하는 버그 재현
코드의 잠재적 문제 분석복잡한 시스템 컨텍스트 이해

4.2 AI에게 질문하는 올바른 자세

나쁜 질문:

"코드가 오류 나요, 좀 봐주세요"

좋은 질문:

"React로 폼 컴포넌트를 작성하고 있는데 제출 시 TypeError: Cannot read properties of undefined (reading 'email') 오류가 발생합니다. 관련 코드는 다음과 같습니다: [코드 첨부]. API가 반환하는 데이터 형식이 올바른 것을 확인했으며, 문제는 프론트엔드 데이터 처리에 있을 것으로 추정됩니다."

질문 템플릿:

1. 무엇을 하고 있는지: [배경]
2. 기대하는 동작: [어떻게 되어야 하는지]
3. 실제 동작: [실제로 어떻게 되는지]
4. 오류 메시지: [전체 오류 내용]
5. 관련 코드: [코드 첨부]
6. 이미 시도한 것: [무엇을 배제했는지]

4.3 AI 디버깅의 함정

AI 디버깅의 세 가지 주의점

  1. AI가 "자신만만하게 헛소리"를 할 수 있음: AI가 제시한 해결책이 합리적으로 보이지만 완전히 틀릴 수 있습니다. 항상 직접 검증하세요.
  2. AI는 여러분의 컨텍스트를 모름: 프로젝트 구조, 의존성 버전, 실행 환경을 알지 못합니다. 충분한 컨텍스트를 제공해야 합니다.
  3. AI에 과도하게 의존하면 디버깅 능력이 퇴화함: 매번 오류가 나면 바로 AI에게 넘기면 영원히 스스로 디버깅하는 법을 배울 수 없습니다. 먼저 스스로 5분간 분석한 후 AI에게 도움을 요청하세요.

4.4 AI + 수동의 최적 조합

버그 발견

1단계: 직접 오류 메시지 읽기 (1분)

2단계: 직접 가설 세우기 (2분)

3단계: 가설 빠르게 검증 (2분)

막혔나요? → 오류 메시지 + 코드 + 분석 내용을 AI에게 전달

AI가 제안 → 합리적인지 판단 → 검증

5. 디버깅 마인드셋과 습관: "불 끄기"에서 "불 예방"으로

가장 좋은 디버깅은 디버깅이 필요 없는 것입니다. 좋은 습관을 들이면 근본적으로 버그를 줄일 수 있습니다.

5.1 방어적 프로그래밍

핵심 아이디어: 코드를 작성할 때 "모든 것이 잘못될 수 있다"고 가정하고 미리 보호 조치를 취합니다.

javascript
// 나쁨: data가 반드시 존재한다고 가정
const name = data.user.name

// 좋음: 방어적 작성법
const name = data?.user?.name ?? '알 수 없는 사용자'
python
# 나쁨: 파일이 반드시 열 수 있다고 가정
content = open('config.json').read()

# 좋음: 방어적 작성법
try:
    content = open('config.json').read()
except FileNotFoundError:
    print("설정 파일이 존재하지 않아 기본 설정을 사용합니다")
    content = '{}'

5.2 좋은 로그 작성

로그는 "사후 디버깅"의 핵심입니다. 프로덕션 환경에서는 중단점을 설정할 수 없고 로그에만 의존해야 합니다.

로그 레벨용도예시
DEBUG개발 중 상세 정보변수 값, 함수 매개변수
INFO정상적인 비즈니스 흐름"사용자 로그인 성공", "주문 생성"
WARN기능에는 영향 없지만 주의 필요"캐시 미스", "2번째 재시도"
ERROR오류 발생, 처리 필요"데이터베이스 연결 실패", "API 시간 초과"

좋은 로그의 기준

좋은 로그 한 줄은 다음을 답해야 합니다: 언제, 어디서, 무슨 일이, 핵심 데이터는 무엇인지.

[2025-01-15 14:30:22] [ERROR] [OrderService] 주문 생성 실패
  사용자 ID: 12345, 상품 ID: 67890, 원인: 재고 부족

5.3 디버깅 체크리스트

버그를 발견했을 때, 이 순서대로 확인합니다:

  1. 오류 메시지 읽기: 오류 유형, 파일, 줄 번호
  2. 최근에 무엇을 변경했는가?: git diff로 최근 변경 사항 확인
  3. 재현 가능한가?: 안정적인 재현 단계 찾기
  4. 범위 축소: 이분법이나 최소 재현으로 위치 파악
  5. 가설 세우고 검증: 한 번에 하나의 변수만 변경
  6. 수정 후 회귀 테스트: 수정으로 새로운 문제가 발생하지 않았는지 확인

5.4 초보자가 자주 빠지는 디버깅 함정

함정올바른做法
오류 메시지를 보지 않고 코드 수정 시작먼저 오류 메시지를 끝까지 읽기
여러 곳을 동시에 수정한 곳만 수정하고 검증 후 다음 곳 수정
수정 후 테스트하지 않고 커밋매번 수정 후 테스트 실행
자신의 컴퓨터에서만 테스트다른 환경 고려 (브라우저, 시스템, 네트워크)
디버깅 후 console.log 정리하지 않음커밋 전 모든 디버그 코드 삭제
문제 발생 시 재시작/재설치먼저 문제 원인 이해, 재시작은 임시 방편

6. 요약

디버깅은 기술이며 의도적인 연습이 필요합니다. 이 장의 핵심 요점을 정리하면:

  1. 디버깅은 과학적 방법: 관찰 → 가설 → 실험 → 검증, 운에 맡기는 것이 아님
  2. 오류 메시지는 친구: "무엇이, 어디서, 왜"를 오류에서 추출하는 방법 배우기
  3. 고전적 방법은 영원히 유효: 이분법, 고무오리, 최소 재현은 모든 디버깅의 기초
  4. 도구는 올바른 시나리오에 사용: console.log로 빠른 검증, 중단점으로 심층 분석, Network로 API 문제 해결
  5. AI는 조수지 목발이 아님: 먼저 스스로 분석하고, 다음에 AI 보조를 받고, 마지막에 직접 검증
  6. 불 예방이 불 끄기보다 낫다: 방어적 프로그래밍, 좋은 로그 습관이 근본적으로 버그 감소

이 말을 기억하세요

모든 버그는 학습 기회입니다. 여러분이 수정한 모든 버그는 "패턴 인식" 능력을 구축하고 있습니다 — 다음에 비슷한 문제를 만나면 더 빠르게 원인을 파악할 수 있을 것입니다.


추가 읽기