본문 바로가기
CS/Code Complete

Chapter 23. Debugging(디버깅)

by 아찌방 2025. 2. 23.

 

Debugging? => 오류의 근본 원인을 식별하고 이를 수정하는 과정

 

오류를 처음 탐지하는 과정인 테스트와는 다른 거임. OK?

일부 프로젝트에서는 디버깅이 전체 개발 시간의 50%를 차지하기도 함.

=> 보통 사소한 실수, 오타로 발생


23.1 Overview of Debugging Issues (디버깅 문제 개요)

 

Role of Debugging in Software Quality (디버깅의 소프트웨어 품질에서의 역할)
디버깅은 테스트처럼 소프트웨어의 품질을 향상시키는 방법이 아니라 결함을 진단하는 방법

=> 디버깅 하는 일 없도록 해라

=> 어떻게? 요구사항을 신중하게 개발하고, 잘 설계하고, 고품질의 코딩 관행을 사용해서

디버깅은 마지막 수단이어야 한다.

 

Defects as Opportunities (결함을 기회로 삼기)

가장 좋은 것은 결함을 고치는 것이 아니라 피하는 것

but, 사람은 실수 할 수 있음

결함을 발견하면 이를 통해 프로그램, 자신의 실수, 코드 품질, 문제 해결 방식 등을 배우는 기회로 삼으셈

 

An Ineffective Approach (비효율적인 접근법)

 

The Devil’s Guide to Debugging (디버깅의 악마의 지침)

 

1. 결함을 추측으로 찾기

2. 문제를 이해하려고 하지 않기 => 결함을 찾는 것을 우선시(안 좋음)

3. 가장 눈에 띄는 방법으로 오류 수정하기

x = Compute(y)
if (y == 17):
    x = $25.15  # Compute()는 y = 17일 때 작동하지 않으므로 여기서 수정

 

y가 17일 때 왜 문제인지 찾는 것이 아니라, 회피

 


23.2 Finding a Defect (결함 찾기)

디버깅은 단순히 결함을 찾고 수정하는 것 이상으로, 과학적 방법에 따라 데이터를 수집하고 가설을 세워 이를 입증하는 과정임

결함을 찾고 이해하는 것이 일반적으로 작업의 90%

 

디버깅의 과학적 방법
디버깅에 적용되는 고전적인 과학적 방법의 단계는 다음과 같습니다:

  1. 반복 가능한 실험을 통해 데이터를 수집합니다.
  2. 관련 데이터를 설명할 수 있는 가설을 세웁니다.
  3. 가설을 입증하거나 반증할 실험을 설계합니다.
  4. 가설을 입증하거나 반증합니다.
  5. 필요한 만큼 반복합니다.

효과적인 결함 찾기

  1. 오류를 안정화합니다.
  2. 오류의 원본(“결함”)을 찾습니다. a. 결함을 발생시키는 데이터를 수집합니다. b. 수집된 데이터를 분석하고 결함에 대한 가설을 세웁니다. c. 가설을 입증하거나 반증할 방법을 결정합니다. 프로그램을 테스트하거나 코드를 검사할 수 있습니다. d. 2(c)에서 결정된 절차를 사용하여 가설을 입증하거나 반증합니다.
  3. 결함을 수정합니다.
  4. 수정 사항을 테스트합니다.
  5. 유사한 오류를 찾습니다.

Stabilizing the Error (오류 안정화)

결함이 일관되게 발생하지 않으면 진단하기 거의 불가능
=> 간헐적인 결함이 예측 가능하게 발생하도록 만드는 것은 디버깅에서 가장 어려운 작업 중 하나

 

일관되지 않게 발생하는 오류는 보통 초기화 오류, 타이밍 문제, 또는 댕글링 포인터 문제일 수 있음

예를 들어, 합계를 계산하는 값이 때로는 맞고 때로는 틀리다면, 계산에 참여하는 변수 중 하나가 제대로 초기화되지 않았을 가능성이 높다. 대부분의 경우 변수는 0에서 시작하는 것처럼 보임. 만약 문제가 이상하고 예측할 수 없는 현상이라면, 포인터를 사용하는 경우가 많고, 이때는 초기화되지 않은 포인터를 사용하거나 포인터가 가리키는 메모리가 이미 해제되었을 가능성이 큽니다.

 

테스트 케이스 단순화

오류를 발생시키는 테스트 케이스를 찾는게 먼저 필요

10가지 요소가 의심 => 하나씩 값을 변경하면서 테스트

=> 뭐가 주요 요소인지 파악 가능

 

Tips for Finding Defects (결함 찾기 위한 팁)

  1. 가설을 세울 때 데이터 활용: 결함의 원인을 추론할 때 가능한 많은 데이터를 고려하고, 가설이 맞지 않으면 새로운 가설을 세운다.
  2. 테스트 케이스 다듬기: 에러를 찾지 못했다면, 테스트 케이스를 더 세밀하게 다듬어 문제를 추적한다.
  3. 단위 테스트 활용: 작은 코드 조각에서 결함을 찾는 것이 더 쉽다. 단위 테스트로 코드의 일부만 테스트해본다.
  4. 디버깅 도구 활용: 메모리 검사기나 디버거와 같은 도구를 사용해 오류를 쉽게 찾아낸다.
  5. 여러 방식으로 에러 재현: 유사한 테스트 케이스들을 통해 에러를 여러 방식으로 재현해보며 원인을 좁혀 나간다.
  6. 새로운 데이터로 가설 확장: 기존에 확인된 테스트 케이스 외에도 다른 테스트 케이스를 시도하여 추가 데이터를 얻고, 이를 통해 새로운 가설을 만들어낸다.
  7. 부정적 테스트 결과 활용: 테스트 결과로 가설이 틀렸다는 것을 알면, 오류가 아닌 부분을 좁힐 수 있다.
  8. 가설 브레인스토밍: 처음 생각한 가설에만 집중하지 않고 여러 가설을 떠올려서 그에 맞는 테스트 케이스를 생각한다.
  9. 공백을 두고 생각하기: 문제에 집중하다 보면 생각이 막히기 때문에 잠시 휴식을 취하고 나서 해결책을 떠올릴 수 있다.

Brute-Force Debugging (브루트 포스 디버깅)

지루하고, 고되고, 시간이 많이 소모되지만 문제를 해결하는 데 확실히 효과적

 

빠르고 간단한 디버깅

을 시도할 때는 시간을 제한하고, 시간이 초과되면 다른 방법으로 문제를 해결

 

브루트 포스 기법 목록을 작성하여 어려운 오류에 대해 보장된 해결책을 미리 준비하는 것이 중요

 

Syntax Errors (구문 오류)

컴파일러의 진단 메시지가 개선되면서 최근에는 거의 없음

 

구문 오류를 빨리 해결하는 가이드

1. 컴파일러 메시지의 라인 번호와 오류 메시지를 모두 신뢰하지 말고, 오류가 발생한 위치 앞뒤를 확인해 보기

2. 다중 오류 메시지에 대해서는 첫 번째 오류를 수정한 후 다시 컴파일하여 문제를 해결하기

3. 분할 정복 방법을 사용하여 프로그램을 섹션별로 나누고, 오류를 빠르게 찾아보세요.

4.  잘못된 주석이나 따옴표 문제를 찾아 해결하려면 자동화된 편집기를 활용하고, 특정 코드 구문을 사용하여 오류를 좁힐 수 있습니다.

 

 


23.3 Fixing a Defect (결함 수정)

디버깅에서 가장 어려운 부분은 결함을 찾는 것

결함을 찾은 후 수정하는 일은 상대적으로 쉬운 작업이지만, 그만큼 실수가 발생하기 쉬움.

(한 연구에서는 결함 수정이 처음에는 50% 이상의 확률로 잘못된다고 함 ㄷㄷ)

 

 

1. 문제의 근본 원인을 이해하고 수정 => 잘못된 가설을 세우지 않도록 주의

2. 문제가 발생한 주변 코드를 이해하고 그 부근의 코드가 전체 프로그램에 미치는 영향을 고려

3. 진단이 정확한지 확인한 후 수정

4. 서두르지 말고 여유를 가지며 확실하게 수정이 이루어졌는지 검토

5. 원본 코드를 저장하여 필요시 비교할 수 있게 만들기

6.증상이 아니라 원인을 수정하세요. 하드코딩과 같은 임시방편적인 수정은 문제를 악화시킬 수 있음

 

 

증상 수정을 위한 코드 변경의 위험

무작위로 코드를 변경하여 문제를 해결하려는 접근은 비효율적

=>

1. 확신을 가지고 수정: 문제를 제대로 이해하고, 무작위로 변경하지 않으며 확신을 가지고 수정

2. 검증 및 회귀 테스트: 수정 후 충분히 검증하고, 자동화된 회귀 테스트로 부작용이 없는지 확인

3. 유사한 결함 찾기: 한 가지 결함을 수정한 뒤, 비슷한 오류가 다른 곳에 있는지 찾아서 수정

 

 


23.4 Psychological Considerations in Debugging (디버깅에서의 심리적 고려사항)

How “Psychological Set” Contributes to Debugging Blindness ("심리 세트"가 실명 디버깅에 기여하는 방법)

"심리적 세트"란 사람이 기대하는 것을 보는 현상

num이라는 변수를 보면 Number의 줄임말이구나 하는 거

 

대충 보고 "아 이거는 이렇게 되겠지" 생각하면서 디버깅하면 망함

 

코드의 형식, 주석, 변수명, 함수명 등은 디버깅에 중요한 영향미침 => 주의할 것

현재 디버깅하는 것과 관련 없는 부분을 제외하기 => 근데 실수로 관련있는 거 제외 할 수 있으니 주의

 

How “Psychological Distance” Can Help (심리적 거리가 디버깅에 미치는 영향)

심리적 거리는 두 항목이 얼마나 쉽게 구별될 수 있는지를 정의

 

두 단어가 비슷해서 혼동할 가능성이 높다 => 심리적 거리가 작음.

두 단어가 딱 봐도 다르다 => 심리적 거리 멈

 

변수 이름이나 함수 이름 사이의 심리적 거리가 충분히 크지 않으면, 비슷한 이름을 쉽게 혼동할 수 있음 => 주의해서 충분히 구분가게 할 것

 


23.5 Debugging Tools—Obvious and Not-So-Obvious (디버깅 도구—명백한 것과 그렇지 않은 것)

디버깅에 도움을 주는 도구 또는 방법

1. 소스 코드 비교 도구 : 코드 변경 사항을 추적

2. 컴파일러 경고 메시지 : 중요한 오류를 피하는 데 유효

3. 프로젝트 전반의 컴파일 시간 설정 표준화

4. 확장된 구문 및 논리 검사 : lint 같은 도구는 코드의 미세한 문제를 찾아 줌

5. 실행 프로파일러 : 성능 문제 추적

 

테스트 프레임워크/스캐폴딩

스캐폴딩(Scaffolding)과 관련하여, 문제 있는 코드를 분리하여 독립적으로 테스트하고 실행하는 것이 오류를 해결하는 데 가장 효과적일 수 있음

 

디버거

많은 도움을 주고 있으나 비판적인 의견도 존재

=> 단기적인 해결책에 의존하게 만들고, 체계적인 설계를 방해. 대신, 두뇌를 활용하여 프로그램을 실행하고 결함을 찾는 것이 더 효과적이라고 주장

 

잘 생각해서 잘 사용할 것

 

 

 

 

 

 

 

 

 

 

728x90