💻 프로그래밍

Bug

버그

소프트웨어 오류. 논리 버그, 런타임 버그, 컴파일 버그로 구분. 디버깅으로 해결.

📖 상세 설명

버그(Bug)는 소프트웨어가 예상과 다르게 동작하게 만드는 오류나 결함을 의미합니다. 1947년 하버드 대학교의 Grace Hopper가 Mark II 컴퓨터에서 실제 나방(moth)이 릴레이에 끼어 오작동을 일으킨 것을 발견하고 "디버깅(debugging)"이라는 용어를 처음 사용하면서 이 용어가 널리 퍼지게 되었습니다.

버그는 크게 세 가지로 분류됩니다. 컴파일 버그(Compile Bug)는 문법 오류로 프로그램이 빌드되지 않는 경우이고, 런타임 버그(Runtime Bug)는 실행 중 크래시나 예외를 발생시킵니다. 논리 버그(Logic Bug)는 프로그램이 실행은 되지만 잘못된 결과를 출력하는 가장 찾기 어려운 유형입니다.

버그의 심각도는 크리티컬(Critical), 메이저(Major), 마이너(Minor), 트리비얼(Trivial)로 구분됩니다. 크리티컬 버그는 시스템 전체가 멈추거나 데이터 손실이 발생하는 경우이며, 마이너 버그는 기능에 큰 영향이 없는 사소한 문제입니다. 버그 추적 시스템(Bug Tracking System)을 통해 체계적으로 관리합니다.

버그를 찾고 수정하는 과정을 디버깅(Debugging)이라고 합니다. 현대 개발에서는 IDE의 디버거, 로깅, 단위 테스트, 정적 분석 도구 등을 활용하여 버그를 예방하고 빠르게 발견합니다. "버그 없는 소프트웨어는 없다"는 말처럼, 버그 관리는 소프트웨어 품질의 핵심입니다.

💻 코드 예제

❌ 버그가 있는 코드
# 버그 1: Off-by-one 에러 (경계값 오류)
def get_average(scores):
    total = 0
    for i in range(1, len(scores)):  # 버그: 0이 아닌 1부터 시작
        total += scores[i]
    return total / len(scores)  # 버그: 잘못된 평균 계산

# 버그 2: None 체크 누락
def get_user_name(user):
    return user.name.upper()  # 버그: user가 None이면 크래시

# 버그 3: 부동소수점 비교 오류
def check_balance(amount):
    if 0.1 + 0.2 == 0.3:  # 버그: False 반환 (부동소수점 오차)
        return "정확함"
    return "오차 발생"

print(get_average([90, 85, 88, 92]))  # 88.33이 아닌 66.25 출력
print(get_user_name(None))  # AttributeError 발생
print(check_balance(100))  # "오차 발생" 출력
✅ 수정된 코드
# 수정 1: 올바른 범위와 계산
def get_average(scores):
    if not scores:  # 빈 리스트 체크 추가
        return 0
    total = 0
    for i in range(0, len(scores)):  # 수정: 0부터 시작
        total += scores[i]
    return total / len(scores)
    # 또는 더 간단하게: return sum(scores) / len(scores)

# 수정 2: 방어적 프로그래밍
def get_user_name(user):
    if user is None:  # 수정: None 체크
        return "Unknown"
    if not hasattr(user, 'name') or user.name is None:
        return "Unknown"
    return user.name.upper()

# 수정 3: 부동소수점 안전 비교
import math

def check_balance(amount):
    if math.isclose(0.1 + 0.2, 0.3):  # 수정: isclose() 사용
        return "정확함"
    return "오차 발생"

print(get_average([90, 85, 88, 92]))  # 88.75 출력 (정확)
print(get_user_name(None))  # "Unknown" 출력 (안전)
print(check_balance(100))  # "정확함" 출력 (정확)

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"이번 스프린트에서 크리티컬 버그 2건이 발견됐는데, 하나는 결제 모듈의 race condition이고 다른 하나는 세션 타임아웃 처리 누락입니다. 핫픽스로 오늘 중 배포하겠습니다."
💬 면접에서
"프로덕션에서 발생한 어려운 버그를 해결한 경험이 있습니다. 특정 시간대에만 발생하는 메모리 누수였는데, 프로파일링 도구와 로그 분석을 통해 타이머 콜백에서 클로저가 객체를 계속 참조하고 있던 것을 발견했습니다."
💬 코드 리뷰에서
"여기 null 체크 없이 바로 메서드를 호출하고 있네요. 이런 패턴은 NullPointerException 버그의 원인이 됩니다. Optional이나 null-safe 연산자를 사용하는 게 좋겠습니다."

⚠️ 흔한 실수 & 주의사항

버그를 숨기거나 미루지 마세요

"나중에 고치지"라는 생각으로 미루면 버그가 쌓여 기술 부채가 됩니다. 발견 즉시 버그 트래커에 등록하고, 심각도에 따라 우선순위를 정해 처리하세요.

증상만 고치지 말고 근본 원인을 찾으세요

에러가 발생하는 곳에 try-catch만 추가하면 버그가 숨겨질 뿐입니다. Root Cause Analysis를 통해 왜 그 버그가 발생했는지 파악하고 근본적으로 해결하세요.

재현 단계를 명확히 문서화하세요

버그 리포트에는 "어떤 환경에서, 어떤 순서로 조작하면, 어떤 결과가 나타나는지"를 명확히 기록하세요. 재현 가능한 버그는 해결하기 훨씬 쉽습니다.

버그 수정 후 회귀 테스트를 추가하세요

버그를 수정할 때 해당 케이스를 테스트 코드로 작성하면 같은 버그가 다시 발생하는 것(regression)을 방지할 수 있습니다.

🔗 관련 용어

📚 더 배우기