아무도 말하고 싶어 하지 않는 숫자
비공식 코드 리뷰는 검토 대상 코드에 존재하는 결함의 15~30%를 발견한다. 이는 의견이 아니다. 40년이 넘는 기간, 여러 기업, 수십 개의 연구를 통해 재현된 결과다.
마이클 페이건은 1976년 IBM에서 이를 기록했다. 1987년 AT&T 벨 연구소의 연구에서는 20%를 발견했다. 1996년 HP 연구에서는 25%를 발견했다. 2013년 Microsoft Research의 현대 코드 리뷰에 관한 논문에서도 거의 비슷한 범위가 나타났다. 도구는 펀치 카드에서 GitHub로 바뀌었지만, 인간의 성능 곡선은 움직이지 않았다.
팀이 풀 리퀘스트 리뷰를 품질의 마지막 방패라고 생각한다면, 데이터는 이렇게 말한다. 약 4분의 1 버그만 잡고 있으며, 나머지 4분의 3은 출시된다고.
숫자의 출처
페이건의 원래 방법론은 단순하고도 가혹했다. 그는 코드에 알려진 결함을 주입하고, 리뷰 프로세스를 실행한 뒤 리뷰어가 몇 개를 찾았는지 세었다. 그런 다음 테스트나 본격 인시던트, 고객 보고를 통해 팀이 최종적으로 찾은 전체 결함 수와 비교했다. 리뷰에서 발견된 수를 전체 결함 수로 나눈 비율이 제거율(removal rate)이 되었다.
핵심 통찰은 분모가 중요하다는 것이다. 10개의 결함을 찾은 리뷰는 좋게 들리지만, 그 파일에 50개가 있었다는 사실을 알면 이야기가 달라진다. 페이건은 전체 분모를 측정했다. 오늘날 대부분의 팀은 그렇게 하지 않는다.
후속 연구들도 유사한 설계를 사용했다. 연구자들은 결함을 심고, 리뷰 유형을 비교하거나, 본격 환경에서 결함을 역추적하여 어디서 잡을 수 있었는지 살펴봤다. 결과는 밀집해 있다.
| 리뷰 유형 | 결함 제거율 | 주요 연구 |
|---|---|---|
| 리뷰 없음 | 0% | 베이스라인 |
| 비공식 / PR 리뷰 | 15-30% | Fagan 1976, Porter 1995, Microsoft 2013 |
| 구조화된 워크스루 | 30-50% | Yourdon 1979, Weller 1993 |
| 페이건 인스펙션 | 60-90% | Fagan 1976, Russell 1991, NASA 2002 |
비공식 리뷰와 구조화된 리뷰 사이의 격차는 작지 않다. 결함 이탈률(defect escape rate)에서 3배의 차이가 난다.
PR 리뷰가 왜 이렇게 성능이 낮은가
문제는 리뷰어가 일을 못해서가 아니다. 문제는 풀 리퀘스트 리뷰가 결함을 찾도록 설계되지 않았다는 것이다. 그것은 두 사람이 코드를 머지할 수 있다는 데 동의하도록 설계되었다.
전형적인 PR 리뷰에서 실제로 일어나는 일을 살펴보자. 리뷰어는 diff를 연다. 요약을 읽고, 추가분을 훑어보고, 테스트가 통과하는지 확인하고, 명백히 잘못된 것을 찾는다. 이 과정에 5~15분이 걸린다. 그런 다음 승인한다.
이 프로세스는 철저함이 아닌 속도에 최적화되어 있다. 준비 시간이 없다. 리뷰어는 주변 코드를 읽지 않았고, 데이터 흐름을 추적하지 않았으며, 변경 사항에 대한 멘탈 모델을 구축하지 않았다. 그는 화면上的 diff에 반응하고 있을 뿐인데, 인간의 뇌는 그 형식에서 버그를 찾는 데 능숙하지 않다.
2015년 Bacchelli와 Bird의 Microsoft 연구에서는, 가장 흔한 리뷰 댓글 카테고리가 결함과 전혀 관련이 없는 것으로 나타났다. 상위 카테고리는 의도에 대한 질문, 명확화 요청, 개선 제안이었다. 실제 결함 발견은 댓글의 소수를 차지했다. 이 도구는 품질 게이트가 아닌 커뮤니케이션 채널로 기능하고 있었다.
도구가 무엇을 하는지 안다면 괜찮다. 다른 일을 한다고 생각하면 위험하다.
구조화된 리뷰가 다른 점
페이건 인스펙션과 다른 구조화된 리뷰 방법은 사람이 아닌 프로세스 설계를 바꿈으로써 더 높은 제거율을 달성한다.
가장 큰 지점은 개별 준비다. 페이건 인스펙션에서는 그룹 토의 전에 각 리뷰어가 자료에 집중하는 시간을 보낸다. 페이건은 준비된 검사자가 준비 없이 들어온 사람보다 약 2배 많은 결함을 찾았다는 것을 발견했다. PR 모델은 기본적으로 준비 없이 들어오는 모델이다.
두 번째 지점은 속도다. 페이건은 리뷰 시속 100~125줄의 코드를 권장했다. 대부분의 PR 리뷰어는 그 속도의 10배로 처리한다. 속도는 탐지를 죽인다. 뇌는 실제로 있는 것을 읽는 대신 예상되는 패턴을 채워 넣는다.
세 번째 지점은 집중력이다. 페이건 회의에는 단 하나의 목적이 있다. 결함을 기록하는 것이다. 설계 논쟁은 없다. 해결책 제안은 없다. 스타일 논의는 없다. PR 스레드는 일상적으로 아키텍처 의견으로 빗나가며, 이는 null dereference를 찾을 수 있었을 동일한 인지 예산을 소비한다.
네 번째 지점은 독자 역할이다. 코드를 작성하지 않은 사람이 그것을 소리 내어 다시 말하게 하면, 그룹은 훑어보는 속도가 아닌 이해 속도로 처리하게 된다. 화면上的 diff는 지루한 부분을 뛰어넘게 한다. 말하는 사람은 뛰어넘지 않는다.
비용 논리는 뒤집혀 있다
구조화된 리뷰에 대한 흔한 반대는 비용이다. 페이건 인스펙션은 수백 줄의 코드에 대해 4~6인시를 소비한다. PR 리뷰는 한 사람의 15분을 소비한다. 계산은 명백해 보인다.
명백해 보이며, 틀렸다.
페이건은 또한 다른 단계에서 결함을 찾는 비용을 측정했다. 인스펙션 중 발견된 결함의 비용은 테스트 중 발견했을 때의 약 10분의 1이었다. 본격으로 유출되면 비율은 20~30대 1로 커졌다. 2002년 NASA 연구에서는 인스펙션에 소비된 1시간당 평균 33시간의 후속 유지보수 작업을 예방한 것으로 나타났다.
절약은 보이지 않는다. 예방한 버그는 측정할 수 없다. 회의 비용은 즉각적이고 명백하다. 그래서 조직들은 데이터가 결국 더 많은 비용이 든다고 말해도 보이지 않는 품질보다 보이는 속도를 최적화한다.
데이터에 기반한 중간안
IBM의 회의 문화가 없어도 대부분의 이익을 얻을 수 있다. 숫자를 실제로 움직이는 프로세스 특성을 빌리고, 그렇지 않은 것은 버리면 된다.
데이터가 중요하다고 말하는 것은 다음과 같다.
-
준비 시간. 리뷰어가 댓글을 달기 전에 코드에 시간을 보내도록 요구하라. 단 10분의 집중 독서도 훑어보기를 이긴다.
-
속도 제한. 중요한 파일에 대해 최대 리뷰 속도를 강제하라. 리뷰어가 500줄 변경을 5분 만에 승인하면, 그것은 성실함이 아닌 데이터다.
-
결함 전용 집중. 스타일과 아키텍처 피드백을 결함 탐색에서 분리하라. 전자에는 자동 포매터를 사용하고, 후자에는 인간의 주의를 할당하라.
-
체크리스트. 페이건은 체크리스트 주도 리뷰어가 직관 주도 리뷰어가 지나친 결함을 찾았다는 것을 발견했다. 체크리스트는 전문성이 맹점을 만들기 때문에 존재한다.
다음은 Git 히스토리에서 리뷰 깊이를 측정하는 경량 스크립트다. 리뷰가 철저할 만큼 충분한 시간이 있었는지 추정한다.
#!/usr/bin/env python3
"""Estimate review depth from git history."""
import subprocess
import sys
from datetime import datetime, timezone
def get_commit_info(commit_hash: str) -> dict:
"""Return author, committer, and timestamps for a commit."""
fmt = "%H|%an|%cn|%ad|%cd"
result = subprocess.run(
["git", "log", "-1", f"--format={fmt}", commit_hash],
capture_output=True,
text=True,
check=True,
)
parts = result.stdout.strip().split("|")
return {
"hash": parts[0],
"author": parts[1],
"committer": parts[2],
"author_date": datetime.strptime(parts[3], "%a %b %d %H:%M:%S %Y %z"),
"commit_date": datetime.strptime(parts[4], "%a %b %d %H:%M:%S %Y %z"),
}
def get_lines_changed(commit_hash: str) -> int:
"""Count total lines added + deleted in a commit."""
result = subprocess.run(
["git", "diff", f"{commit_hash}^", commit_hash, "--stat"],
capture_output=True,
text=True,
check=True,
)
# Last line of --stat contains totals like "3 files changed, 42 insertions(+), 7 deletions(-)"
for line in reversed(result.stdout.strip().splitlines()):
line = line.strip()
if "insertions" in line or "deletions" in line:
# Extract numbers roughly
parts = line.split(",")
total = 0
for part in parts:
digits = "".join(ch for ch in part if ch.isdigit())
if digits:
total += int(digits)
return total
return 0
def estimate_review_depth(commit_hash: str) -> dict:
"""Estimate whether a commit had time for thorough review.
Returns lines changed, time between author and commit dates
(a rough proxy for review duration), and a verdict.
"""
info = get_commit_info(commit_hash)
lines = get_lines_changed(commit_hash)
# Time between author date and commit date is a proxy for review time
# In many workflows, commit date reflects when the merge happened
review_seconds = (info["commit_date"] - info["author_date"]).total_seconds()
review_hours = review_seconds / 3600
# Fagan recommended 100-125 lines/hour for thorough review
fagan_rate = 125
needed_hours = lines / fagan_rate if lines else 0
verdict = "insufficient"
if review_hours >= needed_hours:
verdict = "adequate"
if review_hours >= needed_hours * 2:
verdict = "thorough"
return {
"hash": commit_hash[:8],
"lines": lines,
"review_hours": round(review_hours, 2),
"needed_hours": round(needed_hours, 2),
"verdict": verdict,
}
if __name__ == "__main__":
commit = sys.argv[1] if len(sys.argv) > 1 else "HEAD"
result = estimate_review_depth(commit)
print(f"Commit: {result['hash']}")
print(f"Lines: {result['lines']}")
print(f"Review time: {result['review_hours']} hours")
print(f"Fagan time: {result['needed_hours']} hours")
print(f"Verdict: {result['verdict']}")
review_depth.py로 저장하고 실행하라.
python review_depth.py abc1234
출력은 해당 커밋이 페이건의 기준으로 철저히 리뷰될 만큼 충분한 시간이 있었는지 알려줄 것이다. 대부분의 커밋은 insufficient라고 할 것이다. 그것이 핵심이다. 데이터는 수십 년 전부터 우리에게 이것을 말해왔지만, 우리는 더 나은 파이프라인이 아닌 더 빠른 파이프라인을 계속 만들고 있다.
팀에 대한 의미
풀 리퀘스트 리뷰는 쓸모없지 않다. 공유 맥락을 구축하고, 지식을 전파하며, 명백한 실수를 잡는다. 하지만 데이터는 그것이 하지 않는 것에 대해 명확하다. 그것은 대다수의 결함을 잡지 않는다.
품질 전략이 PR 리뷰를 주요 필터로 의존하고 있다면, 체로 거르고 있는 셈이다. 15~30%의 제거율은 리뷰어의 실패가 아니다. 그것은 프로세스의 특성이다.
이 숫자를 뛰어넘는 팀은 더 똑똑한 사람을 고용하지 않는다. 그들은 프로세스를 바꾼다. 준비 시간을 추가하고, 속도 제한을 강제하고, 결함 발견을 설계 논의에서 분리하며, 직관이 놓치는 곳에 주의를 집중시키는 체크리스트를 사용한다.
모든 diff에 대해 완전한 페이건 인스펙션이 필요한 것은 아니다. 현재 프로세스가 실제로 무엇을 달성하는지 알고, 더 많은 것을 달성하는 척하는 것을 멈춰야 한다.
FAQ
데이터는 풀 리퀘스트 리뷰의 효과에 대해 무엇이라고 하는가?
IBM, AT&T, HP, Microsoft의 다수 연구는 일관되게 비공식 코드 리뷰가 코드에 존재하는 결함의 15~30%를 발견한다고 나타냈다. 이 범위는 1970년대부터 GitHub 기반 워크플로우에 대한 현대 연구까지 안정적으로 유지되었다.
왜 풀 리퀘스트 리뷰는 결함을 그렇게 적게 발견하는가?
PR 리뷰는 체계적인 결함 탐지가 아닌 속도와 머지 승인에 최적화되어 있다. 리뷰어는 일반적으로 리뷰당 5~15분을 소비하고, 준비 시간이 없으며, 연구가 권장하는 속도의 10배로 코드를 처리하고, 그 형식은 깊은 분석이 아닌 훑어보기를 장려한다.
페이건 인스펙션과 같은 구조화된 리뷰는 얼마나 더 나은가?
페이건 인스펙션은 일관되게 6090%의 결함 제거율을 보고하며, 비공식 리뷰보다 대략 34배 더 우수하다. 차이는 개별 준비, 강제된 속도 제한, 역할 분리, 체크리스트 주도 집중, 그리고 해결책을 논쟁하는 대신 결함을 기록하는 회의에서 비롯된다.
PR 리뷰 효과를 개선하는 가장 저렴한 방법은 무엇인가?
댓글 달기 전에 개별 준비를 요구하고, 팀의 일반적인 결함 유형에 맞춘 체크리스트를 사용하고, 스타일 피드백을 결함 탐색에서 분리하고, 중요한 파일의 리뷰 속도를 제한하라. 준비와 집중에 대한 작은 변화만으로도 회의 오버헤드를 추가하지 않고도 숫자를 상당히 움직일 수 있다.
팀의 실제 결함 제거율을 어떻게 측정하는가?
리뷰에서 발견된 결함과 이후 테스트나 본격에서 발견된 결함을 추적하라. 일찍 잡힌 수를 총 발견 수로 나눈 비율이 제거율이다. 대부분의 팀은 이를 추적하지 않으므로 리뷰 효과를 과대평가한다. 각 결함이 어디서 발견되었는지 기록하기 시작한 다음, 월별로 비율을 계산하라.