git blame 고고학자 & 커밋 메시지 'fix' — 기록의 중요성
git blame 고고학자 & 커밋 메시지 'fix' — 기록의 중요성
"3년 전 누가 이 코드를 짰는지 알아내는 것이 오늘의 미션"
git blame: 코드 고고학의 시작
모든 버그에는 역사가 있음.
그리고 그 역사를 발굴하는 도구가 바로 git blame.
어떤 개발자에게 git blame은 범인을 찾는 수사 도구이고, 어떤 개발자에게는 "아 이거 내가 짠 거였구나..."를 깨닫는 자기 성찰의 시간임.
git blame의 철학
git blame의 목적은 범인 찾기가 아님. 진짜 목적은 맥락 복원임.
"이 코드를 왜 이렇게 짰는지" 를 이해하기 위해 커밋 히스토리를 추적하는 것.
blame이라는 이름이 불행한 거지, git context-history 같은 이름이었으면 오해가 덜했을 것임.
git blame 고고학자의 하루
# 09:00 - 이상한 조건문 발견
# if (user.age > 0 && user.age < 200)
# → 나이가 200 미만? 왜 200?
git blame src/validators/user.ts
# a1b2c3d (김개발 2023-03-15) if (user.age > 0 && user.age < 200)
# 09:05 - 커밋 메시지 확인
git show a1b2c3d
# commit a1b2c3d
# Author: 김개발
# Date: 2023-03-15
# Message: "fix"
# 09:06 - ......
# 09:10 - 그 전 커밋 확인
git log --follow -p src/validators/user.ts
# 이전 커밋: user.age > 0 && user.age < 150
# 그 이전: user.age > 0 && user.age < 120
# 최초: user.age > 0
# 09:20 - 역사 추적 완료
# 알고 보니 QA에서 "200살인 사람도 있을 수 있다"는
# 엣지 케이스 리포트가 3번 들어왔고
# 매번 상한을 올린 거였음.
# 근데 커밋 메시지가 전부 "fix" 라서 이유를 알 수 없었음.
# 09:30 - JIRA 티켓 검색. 해당 티켓 없음.
# 슬랙 검색. 대화 이력 삭제됨.
# 김개발한테 물어봄. 퇴사했음.
# → 고고학 실패. 미스터리로 남음.
최악의 커밋 메시지 모음
커밋 메시지는 미래의 나에게 보내는 편지임. 근데 대부분의 개발자는 미래의 자기 자신을 존중하지 않음.
S티어: 정보량 제로
# 실제로 봤던 커밋 메시지들
git log --oneline
# a1b2c3d fix
# b2c3d4e fix
# c3d4e5f fix again
# d4e5f6g fix for real this time
# e5f6g7h ok now it actually works
# f6g7h8i fuck
# g7h8i9j undo fuck
# h8i9j0k asdf
# i9j0k1l .
# j0k1l2m wip
# k1l2m3n stuff
# l2m3n4o things
# m3n4o5p update
# n4o5p6q changes
# o5p6q7r misc
커밋 메시지 범죄 기록
스택오버플로우 2024 설문 기준, 가장 흔한 쓸모없는 커밋 메시지:
- "fix" (36%)
- "update" (22%)
- "wip" (15%)
- "." (8%)
- "asdf" (7%)
- 빈 메시지 (5%)
- "커밋" (3% — 한국 한정)
- 이모지만 있는 경우 (2%)
- "제발 되라" (1%)
- 욕설 (1%)
A티어: 오해를 유발하는 메시지
# 실제로 혼란을 초래한 커밋 메시지들
# "사소한 변경"
# → 실제 변경: 데이터베이스 스키마 변경 + API 엔드포인트 3개 수정
git diff --stat abc123
# 47 files changed, 2,341 insertions(+), 892 deletions(-)
# "사소한" 이 2341줄 추가라니...
# "오타 수정"
# → 실제 변경: 오타 수정 + "겸사겸사" 리팩토링
# 리뷰어: "오타 수정이라길래 LGTM 했는데
# 비즈니스 로직이 바뀌어 있음"
# "성능 개선"
# → 실제 변경: 캐시를 추가했는데 캐시 무효화 로직이 없음
# 3일 후 "왜 데이터가 안 바뀌죠?" 장애 발생
# "코드 정리"
# → 실제 변경: "사용 안 하는 것 같은" 함수 삭제
# 그 함수는 월 1회 배치 작업에서 사용 중이었음
# 월말에 발견됨
B티어: TMI 메시지
# 너무 많은 정보도 문제임
# "2024년 3월 15일 오후 3시 27분, 회의실 B에서 진행된
# 스프린트 리뷰 미팅에서 박 PM이 요청한
# 사용자 프로필 페이지의 프로필 사진 크기를
# 기존 100px에서 120px로 변경하는 작업을
# 김 시니어의 코드 리뷰를 거쳐 수정했습니다.
# 참고로 오늘 점심은 된장찌개였습니다."
# → 유용한 정보: "프로필 사진 크기 100px → 120px"
# → 불필요한 정보: 나머지 전부
좋은 커밋 메시지 작성법
Conventional Commits 형식
# 구조:
# <type>(<scope>): <subject>
#
# <body>
#
# <footer>
# 타입 종류:
# feat: 새 기능
# fix: 버그 수정
# refactor: 리팩토링 (기능 변경 없음)
# docs: 문서 수정
# test: 테스트 추가/수정
# chore: 빌드/설정 변경
# perf: 성능 개선
# style: 코드 스타일 변경 (포맷팅 등)
좋은 커밋 메시지 예시
# 나쁜 예:
# "fix"
# 좋은 예:
git commit -m "fix: prevent negative inventory count in concurrent orders
When multiple orders are placed simultaneously for the same product,
the inventory check and deduction were not atomic, leading to
negative inventory counts.
Added Redis distributed lock to ensure atomic check-and-deduct.
Closes #4521"
# 이 커밋 메시지의 좋은 점:
# 1. 타입이 명확 (fix)
# 2. 한 줄 요약이 구체적
# 3. 본문에서 WHY를 설명 (왜 이렇게 바꿨는지)
# 4. 어떻게 해결했는지 간략히 서술
# 5. 관련 이슈 번호 참조
WHAT vs WHY
# 나쁜 커밋 메시지: WHAT만 있음 (코드를 보면 아는 정보)
# "change timeout from 30 to 60"
# → 코드를 보면 타임아웃이 60이라는 건 알 수 있음
# → 왜 바꿨는지를 모름
# 좋은 커밋 메시지: WHY가 있음 (코드를 봐도 모르는 정보)
# "fix: increase API timeout to 60s to handle slow third-party responses
#
# The payment gateway occasionally takes 40-50s to respond during peak hours.
# The previous 30s timeout was causing false payment failures.
# Confirmed with payment team that 60s is their recommended maximum."
# 차이:
# WHAT: "타임아웃을 60으로 바꿈" → 코드 diff 보면 알 수 있음
# WHY: "결제 게이트웨이가 피크 시간에 40-50초 걸려서" → 코드에 없는 정보
커밋 메시지 황금률
미래의 나에게 보내는 편지라고 생각하기.
6개월 후 git blame으로 이 줄을 찾았을 때, 커밋 메시지만 읽고 "아, 그래서 이렇게 한 거구나"를 이해할 수 있으면 됨.
그때의 나는 지금의 맥락을 전혀 기억하지 못함. JIRA 티켓 번호도 까먹고, 슬랙 대화도 기억 안 남. 커밋 메시지가 유일한 단서임.
git 히스토리 활용법: 고고학 도구 모음
git blame이 삽이라면, 다른 git 명령어들은 고고학의 나머지 도구임.
git log: 연대기 작성
# 특정 파일의 변경 이력
git log --oneline --follow -- src/services/payment.ts
# 특정 함수가 언제 추가됐는지
git log -S "calculateDiscount" --oneline
# 특정 기간의 변경사항
git log --after="2025-01-01" --before="2025-03-01" --oneline
# 특정 사람의 커밋만
git log --author="김개발" --oneline
# 커밋 통계 (누가 얼마나 기여했는지)
git shortlog -sn --no-merges
git bisect: 범인 찾기
# "이 버그가 언제부터 있었지?" 를 찾는 이진 탐색
git bisect start
git bisect bad # 현재 버전: 버그 있음
git bisect good v2.1.0 # 이 버전에서는 정상이었음
# git이 자동으로 중간 커밋을 체크아웃해줌
# 테스트 → bad/good 표시 → 반복
# → O(log n)으로 범인 커밋을 찾음
# 자동화도 가능:
git bisect start HEAD v2.1.0
git bisect run npm test
# → 테스트가 실패하기 시작한 커밋을 자동으로 찾아줌
git reflog: 타임머신
# "아 실수로 브랜치 삭제했는데..."
# "hard reset 잘못했는데..."
# → reflog가 구해줌
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~3 ← 여기서 실수함
# def5678 HEAD@{1}: commit: feat: add payment ← 이걸 살려야 함
# ghi9012 HEAD@{2}: commit: fix: user validation
git checkout def5678 # 삭제된 커밋으로 이동 가능!
# reflog는 로컬에만 있고, 기본 90일간 보관됨
# 즉, 실수해도 90일 안에 발견하면 복구 가능
고급 git blame 테크닉
# 기본 blame
git blame src/payment.ts
# 특정 줄 범위만
git blame -L 42,60 src/payment.ts
# 공백 변경 무시 (포맷팅 커밋 건너뛰기)
git blame -w src/payment.ts
# 코드 이동 감지 (파일 간 복사 추적)
git blame -M src/payment.ts
# 특정 커밋 이전의 blame (역사를 더 깊이 파기)
git blame abc1234^ -- src/payment.ts
# 이 모든 옵션을 조합하면:
git blame -w -M -L 42,60 src/payment.ts
# → 공백 무시 + 코드 이동 추적 + 42-60줄만
# → 진짜 원저자를 찾을 수 있음
git blame 에티켓
git blame으로 범인(?)을 찾았을 때:
올바른 접근: "이 코드의 배경이 궁금한데, 혹시 기억나세요?" 나쁜 접근: "이거 니가 짰는데 왜 이따위로 짠 거야?"
blame은 맥락 복원 도구이지 책임 추궁 도구가 아님. 3년 전의 코드로 지금의 사람을 판단하면 안 됨. 3년 전의 나와 지금의 나도 다른 사람인 것처럼.
좋은 git 히스토리를 만드는 습관
커밋 단위 전략
# 나쁜 커밋 전략: 하루 끝에 한번에 커밋
git add .
git commit -m "오늘 한 거"
# → 30개 파일, 15개 기능, 3개 버그 수정이 한 커밋에
# 좋은 커밋 전략: 논리적 단위로 커밋
git add src/validators/user.ts src/validators/__tests__/user.test.ts
git commit -m "feat: add age range validation (1-150)
Business requirement: age must be between 1 and 150.
Upper bound based on verified oldest person record (Jeanne Calment, 122).
Added 20% buffer for safety.
Refs: JIRA-1234"
git add src/services/user.ts
git commit -m "refactor: extract user creation logic to service layer
Moved user creation from controller to service for better testability.
No behavioral changes."
# 각 커밋이 하나의 논리적 변경 단위 = 역사 추적이 쉬움
대화형 리베이스로 히스토리 정리
# PR 올리기 전에 커밋 히스토리 정리
# 작업 중 커밋들:
# abc1234 wip: 작업 중
# def5678 fix: 오타
# ghi9012 wip: 계속 작업
# jkl3456 fix: 아 이것도 고쳐야 했네
# mno7890 feat: 사용자 검증 완성
# 이런 히스토리는 나중에 blame 해도 도움이 안 됨
# PR 전에 squash/reword 로 정리:
# 정리 후:
# pqr1234 feat: add user age validation with business rules
# stu5678 refactor: extract validation to separate module
# → 깔끔하고 의미 있는 히스토리
커밋 메시지 템플릿
# ~/.gitmessage 파일 생성
# git config --global commit.template ~/.gitmessage
# 템플릿 내용:
# <type>(<scope>): <subject>
#
# Why:
# - 왜 이 변경이 필요한지
#
# What:
# - 어떤 변경을 했는지 (선택사항, 복잡한 경우만)
#
# Refs: JIRA-XXX
# 이 템플릿이 있으면 매번 빈 에디터 대신
# 구조화된 양식이 나와서 좋은 메시지를 쓰게 유도됨
git blame을 넘어서: 코드 고고학의 철학
코드는 대화다
// 코드는 컴퓨터에게 하는 명령이기도 하지만,
// 동시에 미래의 개발자(나 포함)에게 보내는 메시지이기도 함.
// 나쁜 대화:
const x = y * 1.1; // what?
// 좋은 대화:
const TAX_RATE = 0.1;
const priceWithTax = basePrice * (1 + TAX_RATE);
// 코드 + 커밋 메시지 + PR 설명 = 완전한 대화
// 이 세 가지가 합쳐져야 미래의 고고학자가 맥락을 복원할 수 있음
코드 고고학의 가치
// 코드 고고학이 중요한 이유:
// 1. 버그의 근본 원인을 찾을 수 있음
// → "왜 이렇게 했지?" 를 알면 올바른 수정이 가능
// 2. 반복되는 실수를 방지할 수 있음
// → "5년 전에도 같은 시도를 했는데 실패한 기록이 있네"
// 3. 비즈니스 맥락을 이해할 수 있음
// → "이 예외 처리는 2022년 세법 변경 때문이었구나"
// 4. 기술 부채의 원인을 파악할 수 있음
// → "당시에는 급해서 이렇게 했지만, 이제는 제대로 할 수 있겠다"
// 코드 고고학이 불가능한 프로젝트의 현실:
// - 커밋 메시지가 전부 "fix"
// - JIRA 티켓이 삭제됨
// - 원래 개발자가 퇴사함
// - 슬랙 히스토리가 삭제됨
// - 문서가 없음
// → "아무도 모르는 코드" 가 됨
// → 건드리면 뭐가 터질지 모르니까 아무도 안 건드림
// → 영원히 레거시로 남음
퇴사한 사람의 코드
"이 코드 짠 사람 퇴사했는데요..."
이 말은 개발팀에서 가장 무서운 말 중 하나임. 코드는 남아있는데 맥락은 사라진 상태.
이걸 방지하는 유일한 방법:
- 좋은 커밋 메시지
- 코드 리뷰 (리뷰어도 맥락을 공유하게 됨)
- 핵심 결정의 문서화 (ADR — Architecture Decision Records)
- 코드 내 주석 (왜 이렇게 했는지)
사람은 떠나지만, 기록은 남음. 기록이 좋으면 사람이 떠나도 코드는 살아남음.
실전: 커밋 메시지 리팩토링
"fix"만 쓰던 사람이 좋은 커밋 메시지를 쓰기 시작하는 건 코드 품질 향상의 첫 번째 단계임.
# Before → After 변환 예시
# Before: "fix"
# After: "fix: resolve null pointer when user has no profile image
# Users created before 2024-01 don't have profile_image field.
# Added fallback to default avatar URL."
# Before: "update"
# After: "feat: add pagination to user list API
# Added cursor-based pagination with 20 items per page.
# Breaking change: response format changed from array to object."
# Before: "wip"
# After: (커밋하지 않고 작업 계속. 완료 후 의미 있는 커밋)
# Before: "."
# After: (이건 커밋이 아님. git stash 쓰셈)
# Before: "asdf"
# After: (키보드 테스트는 메모장에서 하셈)
오늘부터 시작하기
완벽한 커밋 메시지를 쓸 필요 없음. "fix"보다 나은 한 줄이면 충분함.
최소한의 규칙:
- 무엇을 고쳤는지 한 줄로 (50자 이내)
- 왜 고쳤는지 한 줄 추가 (선택이지만 강추)
- 관련 이슈 번호 있으면 추가
이것만 해도 상위 30%의 커밋 메시지임. "fix" 쓰는 사람보다 100배 나음.
"좋은 코드는 주석이 필요 없다고? 좋은 커밋 메시지가 있으면 주석도 필요 없다." — 그리고 둘 다 없으면 고고학이 필요하다.