Published on

디버깅의 기초

Authors

디버깅의 기초

디버깅은 개발자의 가장 중요한 능력 중 하나다. LLM이 코드를 작성해주는 시대가 되면서 오히려 더욱 중요해졌다. 내 개인적 경험일 수 있지만, 디버깅의 중요도에 비해 체계적으로 가르치거나 배우려는 인식이 부족한 것 같다. 디버깅은 철저히 개인의 경험과 감에만 맡겨지고 있다.

대부분의 프로그래밍 강의는 한 번에 동작하는 프로그램을 만드는 데 초점이 맞춰져 있다. 하지만 실제 개발에서는 인프라 설정부터 프롬프팅, 코딩까지 한 번에 완벽하게 동작하는 일이 드물다. 차분하게 문제를 하나씩 해결할 수 있는 능력은 어떤 작업을 하든 필수적이다.

디버깅의 목표

디버깅의 목표는 명확하다: 모르는 것을 알아내는 것이다. "왜 버그가 발생했을까?"라는 질문에 체계적으로 답하는 과정이다.

디버깅의 3단계 반복 과정

디버깅은 다음 세 가지 질문을 반복하는 과정이다:

  1. 불확실한 영역과 확실한 영역을 구분하기

    • 현재 무엇을 확실히 알고 있는가?
    • 어떤 부분이 불분명한가?
  2. 버그를 찾기 위해 확실하게 알아내야 할 부분은 무엇인지 파악하기

    • 다음에 확인해야 할 가장 중요한 것은?
    • 어떤 정보가 있으면 문제 해결에 도움이 될까?
  3. 확실한 영역을 넓히려면 무엇을 해야 하는지 결정하기

    • 어떤 도구를 사용할 것인가?
    • 어떤 실험을 해볼 것인가?

중요한 원칙: 글로 정리하기

위 세 가지 질문에 대한 답을 글로 명확하게 작성할 수 있어야 한다. 확신이 있는 간단한 상황에서는 굳이 글을 쓸 필요가 없지만, 복잡한 문제에 봉착했을 때는 차분하게 세 가지를 정리해보자.

명시적으로 글로 정리하는 것과 머릿속의 막연한 생각은 천지 차이다. 세 가지 질문에 답할 수 없으면서 디버깅하는 것은 그저 이곳저곳을 랜덤하게 건드리며 문제가 해결되기를 바라는 것과 같다.

핵심 디버깅 실천법

1. 에러 메시지 제대로 읽기

왜 중요한가:

  • 대부분의 에러 메시지에는 중요한 정보가 담겨 있다
  • 확실한 영역을 넓힐 수 있는 가장 직접적인 수단이다

자주 하는 실수: 에러 메시지를 읽지도 않고 코드를 수정하려고 한다. 다음 중 해당하는 것이 있는지 점검해보자:

  • 영어에 대한 막연한 두려움
  • 에러 메시지를 신경 써서 읽어본 경험 부족
  • 빠르게 문제를 해결해야 한다는 조급함

실제 예시:

TypeError: Cannot read property 'name' of undefined

이 메시지는 undefined 객체의 name 속성에 접근하려 했다는 명확한 정보를 제공한다. 어떤 변수가 undefined인지만 찾으면 문제 해결의 실마리를 잡을 수 있다.

2. 디버깅 툴 활용하기

기본 준비:

  • IDE나 개발 환경의 디버깅 도구 사용법을 별도 시간을 내어 익히자
  • Step Over와 Step Into의 차이를 명확히 알아두자
    • Step Over: 함수 호출을 한 번에 실행
    • Step Into: 함수 내부로 들어가서 한 줄씩 실행

3. 머릿속으로 워크플로우 시뮬레이션하기

핵심 질문:

  • "이 함수에 이런 인자가 들어오면 어떻게 동작할까?"
  • "이 조건문에서 어떤 분기를 타게 될까?"

주의사항: 분해할 수 있는 만큼 최대한 분해해서 이해해야 한다. 한 줄에 여러 함수가 중첩되어 있어도 단계별로 정확히 파악할 수 있어야 한다.

// 이런 코드도 단계별로 분해해서 이해하기
const result = users.filter(u => u.age > 18).map(u => u.name).join(', ');

4. 확실하다고 생각한 부분 재검증하기

함정 주의: 내가 확실하다고 생각했던 부분이 틀릴 수 있다.

실제 예시:

  • "이 인자로는 숫자만 들어온다"고 생각했는데 실제로는 문자열이 들어오는 경우
  • "이 API는 항상 성공한다"고 가정했는데 네트워크 오류가 발생하는 경우
  • "이 배열에는 항상 요소가 있다"고 생각했는데 빈 배열인 경우

5. Top-Down과 Bottom-Up 디버깅 구분하기

Bottom-Up 디버깅: 문제가 발생한 지점부터 시작해서 확실한 영역을 넓혀가는 방식

언제 사용하나: 크래시나 예외가 발생한 경우 예시: 널 포인터 예외가 발생 → 해당 변수가 왜 널인지 추적 → 변수를 설정하는 코드 확인

Top-Down 디버깅: 프로그램의 시작점부터 확실한 영역을 넓혀가며 문제 지점에 도달하는 방식

언제 사용하나: 크래시 없이 이상한 동작을 보이는 경우 예시: 계산 결과가 예상과 다름 → 입력값부터 확인 → 각 단계별 계산 과정 추적

6. 러버덕 디버깅 (Rubber Duck Debugging)

방법: 러버덕이나 다른 사람에게 문제 상황을 자세히 설명하는 것

효과:

  • 논리적으로 설명하려다 보면 문제점이 명확해진다
  • 말로 정리하는 과정에서 해결책이 떠오르는 경우가 많다
  • 생각이 정리되면서 다음 시도할 방법들이 자연스럽게 보인다
  • 가능하다면 실제 사람이 들어주는 것이 가장 좋다. 질문이나 피드백을 받을 수 있기 때문이다.