Split View: 가르치는 코드 리뷰, 상처 주는 코드 리뷰 — 같은 지적을 다르게 만드는 것들
가르치는 코드 리뷰, 상처 주는 코드 리뷰 — 같은 지적을 다르게 만드는 것들
들어가며 — 같은 지적, 다른 결과
두 개의 리뷰 코멘트가 있습니다. 하나는 "이거 왜 이렇게 짰어요?"이고, 다른 하나는 "이 함수가 검증과 저장을 같이 하고 있어서, 나중에 검증 규칙만 바꿀 때 저장 로직까지 건드려야 할 것 같아요. 두 개로 나누면 어떨까요?"입니다. 지적하는 대상은 완전히 같습니다. 하지만 첫 번째를 받은 사람은 방어를 준비하고, 두 번째를 받은 사람은 코드를 봅니다.
이 차이는 인성이나 예의의 문제로 설명되곤 하는데, 실은 대부분 형식의 문제입니다. 친절한 사람이 좋은 리뷰를 쓰는 게 아니라, 좋은 형식이 평범한 사람에게서 좋은 리뷰를 뽑아냅니다. 그래서 이 글은 마음가짐보다 형식에 대한 이야기입니다. 승인의 기준, 코멘트의 접두어, 문장의 순서, PR의 크기 — 전부 팀 위키 한 페이지로 합의할 수 있는 것들입니다.
리뷰의 세 가지 목적, 그리고 대부분이 놓치는 두 개
코드 리뷰는 세 가지 일을 동시에 합니다. 결함을 발견하고, 지식을 퍼뜨리고, 코드의 소유권을 나눕니다. 그런데 대부분의 팀은 첫 번째만을 리뷰의 정의로 여기고, 나머지 둘을 부수 효과 취급합니다.
흥미로운 건 데이터가 반대 방향을 가리킨다는 점입니다. 알베르토 바첼리(Alberto Bacchelli)와 크리스천 버드(Christian Bird)는 2013년 마이크로소프트에서 수행한 연구에서, 개발자와 관리자가 리뷰의 목적으로 결함 발견을 가장 먼저 꼽지만 실제로 관측되는 산출물은 그것과 다르다는 점을 보고했습니다. 실제 코멘트의 다수는 결함 지적이 아니라 가독성 개선, 대안 제시, 그리고 코드가 왜 그렇게 생겼는지에 대한 이해 공유였습니다. 케이틀린 사도스키(Caitlin Sadowski) 연구팀이 2018년에 발표한 구글 사례 연구도 비슷한 결론에 도달했습니다. 구글 내부에서 리뷰의 일차적 동기로 인식되는 것은 결함 사냥이 아니라 교육과 규범 유지 쪽에 가까웠습니다.
두 번째 목적, 지식 전파는 이렇게 작동합니다. 리뷰어는 코드를 읽으면서 그 도메인의 지도를 얻습니다. 6개월 뒤 그 코드가 새벽 3시에 터졌을 때, 리뷰에 참여했던 사람이 한 명이라도 있으면 복구 시간이 달라집니다. 이 관점에서 보면 리뷰어를 정할 때 "가장 잘 아는 사람"만 넣는 것은 최적화가 아닙니다. 잘 아는 사람 한 명과 모르는 사람 한 명 조합이 팀 전체로는 더 남는 장사인 경우가 많습니다.
세 번째, 소유권 공유는 사고가 났을 때 드러납니다. 리뷰를 거쳐 머지된 코드는 작성자 개인의 것이 아니라 팀의 결정이 됩니다. 이건 책임 회피 장치가 아니라 실패를 개인의 실수가 아니라 시스템의 문제로 다루기 위한 전제입니다. 개인 탓하기를 멈추는 문화가 왜 회복력의 조건인지는 일어난 일을 자산으로 바꾸는 법 편에서 다뤘습니다.
승인의 기준 — 완벽이 아니라 명확한 개선
리뷰가 지옥이 되는 가장 흔한 경로는 리뷰어가 스스로에게 잘못된 질문을 하는 것입니다. "이 코드가 내가 짰을 코드인가?"라고 물으면 승인은 영원히 오지 않습니다.
구글이 공개한 엔지니어링 관행 문서는 이 지점을 명시적으로 정리해 뒀습니다. 요지는 이렇습니다. 리뷰어는 코드가 완벽해지기를 요구해서는 안 되고, 그 변경이 시스템 전체의 코드 건강도를 명확히 개선한다면 승인해야 한다는 것. 완벽한 코드라는 것은 없고 더 나은 코드가 있을 뿐이라는 문장이 함께 붙어 있습니다. 승인의 질문은 "최선인가"가 아니라 "머지 전보다 나아졌는가"입니다.
이 기준이 왜 중요한가 하면, 반대 기준이 만드는 비용이 눈에 잘 안 보이기 때문입니다. 완벽을 요구하는 리뷰어 밑에서 사람들은 두 가지로 대응합니다. PR을 아주 작게 쪼개는 게 아니라, 리뷰를 아예 피할 수 있는 방식으로 일하기 시작합니다. 리팩터링을 미루고, 그 리뷰어를 리뷰어 목록에서 빼고, 급하다는 이유로 승인을 건너뛸 명분을 찾습니다. 리뷰 품질을 극단으로 밀면 리뷰 자체가 우회됩니다.
다만 이 기준에 예외는 있습니다. 보안, 데이터 정합성, 되돌리기 어려운 마이그레이션, 공개 API의 계약 — 이런 영역에서는 "나아졌으니 통과"가 통하지 않습니다. 차단할 것과 넘길 것의 목록을 팀 차원에서 미리 합의해 두면, 그 판단이 리뷰어 개인의 성격 문제로 오해되지 않습니다.
코멘트의 문법 — 강도 표시와 세 문장 구조
리뷰가 상처를 주는 가장 흔한 이유는 내용이 아니라 강도가 전달되지 않아서입니다. 리뷰어는 "고치면 좋겠지만 안 해도 됨" 정도로 쓴 코멘트를 작성자는 "이걸 고치지 않으면 승인 안 해 줌"으로 읽습니다. 열 개의 코멘트가 달리면 열 개가 전부 최고 강도로 읽힙니다.
해결책은 놀랄 만큼 단순합니다. 접두어를 붙이는 것. 구글의 리뷰 문서에도 사소한 지적에 "nit:"을 붙이라는 관행이 명시돼 있습니다. 팀 사정에 맞게 한글로 써도 되고, 세 단계면 충분합니다.
| 접두어 | 뜻 | 승인 차단 | 예시 |
|---|---|---|---|
| nit | 취향에 가까운 사소한 지적 | 안 함 | nit: 변수명을 복수형으로 하면 읽기 편할 것 같아요 |
| 제안 | 지금 반영하면 좋지만 다음 PR도 괜찮음 | 안 함 | 제안: 이 분기 로직은 별도 함수로 빼면 테스트가 쉬워질 것 같아요 |
| 반드시 | 머지 전에 해결이 필요함 | 함 | 반드시: 이 쿼리에 사용자 ID 필터가 빠져서 다른 계정 데이터가 노출됩니다 |
| 질문 | 판단이 아니라 정보 요청 | 안 함 | 질문: 여기서 재시도를 3회로 잡은 기준이 있을까요 |
접두어가 만드는 진짜 변화는 리뷰어 쪽에도 있습니다. 강도를 붙이려면 리뷰어가 스스로에게 "이게 정말 머지를 막을 일인가"를 물어야 하고, 그 질문만으로 차단성 코멘트의 수가 눈에 띄게 줄어듭니다.
강도를 정했다면 다음은 문장 구조입니다. 관찰, 영향, 제안 순서로 쓰면 평가어가 자연스럽게 빠집니다. "이 코드 별로네요"는 관찰이 없고 영향이 없고 제안도 없는, 순수한 평가입니다. 반면 "이 함수가 예외를 삼키고 있어서(관찰) 장애 시 원인 추적이 어려울 것 같아요(영향). 로깅 후 다시 던지는 건 어떨까요(제안)"는 같은 내용을 협업으로 바꿉니다. 이 구조는 코드 리뷰만의 것이 아니라 일반적인 피드백 문법이기도 합니다. 상황별 문장 패턴은 회사에서 그대로 쓰는 문장들 편에 정리해 뒀습니다.
한 가지 덧붙이면, 잘된 부분을 짚는 코멘트는 예의가 아니라 정보입니다. 리뷰에 부정 신호만 흐르면 작성자는 무엇을 유지해야 하는지 알 수 없습니다. "이 에러 처리 방식은 다른 모듈에도 가져가면 좋겠어요" 같은 한 줄이 팀의 관행을 만듭니다.
PR을 리뷰어를 위해 설계하기 — 크기와 설명
리뷰 품질의 상당 부분은 리뷰어가 아니라 작성자가 결정합니다. 그리고 작성자가 쥔 가장 큰 변수는 크기입니다.
널리 인용되는 숫자가 있습니다. 한 번에 200에서 400줄을 넘어가면 결함 발견 능력이 떨어진다는 것. 출처는 2006년 스마트베어가 시스코에서 수행한 사례 연구입니다. 개발자 약 50명, 코드 320만 줄, 리뷰 2500건 규모의 데이터를 분석해 리뷰 규모가 커질수록 줄당 결함 발견 수가 떨어지고, 시간당 500줄을 넘는 속도나 60분을 넘는 연속 리뷰에서 효과가 급감한다는 결론을 냈습니다.
이 근거의 한계도 같이 알아 둘 필요가 있습니다. 하나의 회사, 하나의 코드베이스, 2006년의 도구 환경에서 나온 단일 사례 연구이고, GitHub 풀 리퀘스트 이전의 관행을 전제합니다. 게다가 핵심 지표인 결함 밀도는 줄 수로 나눈 값이라, 큰 변경에서 값이 낮아지는 것은 부분적으로 정의상 당연한 결과이기도 합니다. 리뷰어가 실제로 보고한 결함만 세었다는 점도 걸립니다. 그러니 200에서 400줄은 물리 법칙이 아니라 경험칙으로 받아들이는 편이 정확합니다.
경험칙이라도 방향은 대체로 맞습니다. 참고로 구글 사례 연구에서 보고된 변경의 중앙값 크기는 수십 줄 수준이었습니다. 우리가 상상하는 것보다 훨씬 작습니다. 실무적으로는 이렇게 접근하면 충분합니다. 리팩터링과 기능 변경을 한 PR에 섞지 않는 것, 자동 포매팅과 실제 로직 변경을 분리해 커밋하는 것, 대규모 변경은 인터페이스를 먼저 머지하고 구현을 뒤에 붙이는 것.
크기를 줄였다면 남은 절반은 설명입니다. 좋은 PR 설명은 세 가지를 담습니다. 왜 이걸 하는가(맥락과 링크), 어떤 선택지를 고려했고 왜 이걸 골랐는가(대안), 어디를 집중해서 봐 달라는가(리뷰 요청 포인트). 특히 세 번째가 비동기 리뷰의 왕복을 결정적으로 줄입니다. "3번 파일의 락 처리만 특히 봐 주세요, 나머지는 기계적 변경입니다"라는 한 줄이 리뷰어의 두 시간과 하루치 왕복을 아낍니다.
리뷰를 받는 쪽의 기술
리뷰 문화는 리뷰어만 잘해서 만들어지지 않습니다. 받는 쪽에도 기술이 있습니다.
첫째, 첫 반응을 지연시키는 것. 코멘트를 읽고 즉시 반박문을 쓰는 것이 가장 흔한 실수입니다. 특히 시간을 들여 짠 코드일수록 지적이 인신공격처럼 읽힙니다. 30분 뒤에 다시 읽으면 같은 문장이 다르게 보이는 경우가 많습니다.
둘째, 동의하지 않을 때 근거를 요청하는 것. "그렇게 하면 성능이 나빠질 것 같은데요"보다 "이 방식을 고른 이유는 A였는데, 제안하신 방식이 그 부분에서 어떤 이점이 있을까요?"가 훨씬 빨리 결론에 도달합니다. 논쟁이 길어지면 형식을 바꾸는 것도 방법입니다. 왕복 세 번을 넘어가면 15분 통화가 스레드 스무 개보다 쌉니다.
셋째, 반영하지 않기로 한 것도 답하는 것. 침묵으로 넘어간 코멘트는 리뷰어에게 무시로 읽힙니다. "이건 다음 PR에서 처리하겠습니다, 티켓 만들어 뒀어요"나 "이건 의도한 동작이라 주석을 추가했습니다" 한 줄이면 충분합니다. 반영률보다 응답률이 신뢰를 만듭니다.
넷째, 배운 것을 명시적으로 말하는 것. "이 패턴 몰랐는데 배웠습니다"라는 한 줄은 아부가 아니라 리뷰어에게 주는 피드백입니다. 리뷰는 대체로 보상이 희박한 노동이고, 그 한 줄이 다음 리뷰의 성실도를 바꿉니다.
리뷰가 권력 게임이 될 때
여기까지가 잘 작동하는 경우의 이야기입니다. 그렇지 않은 경우도 있습니다.
신호는 대체로 비슷합니다. 특정 리뷰어의 PR만 유독 오래 걸린다. 지적의 내용이 리뷰마다 바뀌어서 맞출 수가 없다. 아키텍처 문제는 그냥 넘어가면서 변수명과 공백에는 코멘트가 스무 개 달린다. 마지막 항목은 노스코트 파킨슨(C. Northcote Parkinson)이 1957년에 지적한 자전거 보관소 논쟁과 정확히 같은 구조입니다. 원자로 설계에는 아무도 의견을 못 내지만 자전거 보관소 색깔에는 모두가 의견을 냅니다. 사소한 지적이 몰리는 곳은 대개 아무도 어려운 부분을 읽지 않았다는 신호입니다.
리뷰가 완전히 중립적인 기술 행위가 아니라는 실증 연구도 있습니다. 조시 테렐(Josh Terrell) 연구팀이 2017년에 발표한 깃허브 풀 리퀘스트 분석은, 전체적으로는 여성의 PR 승인율이 더 높았지만 성별이 프로필에서 식별 가능한 외부 기여자의 경우에는 그 우위가 사라진다고 보고했습니다. 이 연구는 성별 추정 방법과 관측 데이터의 인과 해석을 두고 상당한 반박을 받았고, 결론이 확정된 사안은 아닙니다. 다만 확정되지 않았다는 것이 "리뷰는 순수하게 기술적이다"의 근거가 되지도 않습니다.
대응은 개인기보다 제도가 낫습니다. 스타일 논쟁은 사람이 아니라 포매터와 린터가 판정하게 만들 것. 강도 접두어를 팀 규칙으로 못 박아서 차단 권한을 명시화할 것. 승인까지의 시간과 왕복 횟수를 사람별이 아니라 팀 지표로 볼 것. 그리고 반복적으로 특정 사람에게만 다른 기준이 적용된다면, 그건 리뷰 스레드에서 풀 문제가 아니라 매니저와의 1on1에서 다룰 문제입니다.
마치며 — 리뷰는 코드가 아니라 사람을 통과합니다
좋은 리뷰 문화의 지표는 결함 발견 수가 아닙니다. 6개월 뒤 그 코드를 고칠 수 있는 사람이 몇 명 늘었는가입니다. 결함은 테스트와 모니터링으로도 잡히지만, 설계의 맥락이 팀에 퍼지는 통로는 리뷰가 거의 유일합니다.
오늘 당장 바꿀 수 있는 것은 두 가지뿐입니다. 다음 코멘트에 강도 접두어를 붙이는 것, 그리고 다음 PR 설명에 "어디를 봐 달라"는 한 줄을 넣는 것. 문화는 합의문이 아니라 이런 한 줄들이 쌓여서 만들어집니다.
Code Review That Teaches, Code Review That Wounds — What Makes the Same Note Land Differently
Introduction — Same Note, Different Outcome
Take two review comments. One is "why did you write it like this?" The other is "this function is doing validation and persistence together, so changing a validation rule later would mean touching the storage logic too. What if we split it in two?" They point at exactly the same thing. But the person who gets the first one starts preparing a defense, and the person who gets the second one looks at the code.
The difference tends to get explained as a matter of character or manners, when in fact it is mostly a matter of format. Kind people do not write good reviews; good formats extract good reviews out of ordinary people. So this post is about format rather than attitude. The bar for approval, the prefix on a comment, the order of the sentences, the size of the PR — all of them are things a team can settle in one wiki page.
The Three Purposes of Review, and the Two Most Teams Lose
Code review does three things at once. It finds defects, it spreads knowledge, and it distributes ownership of the code. Most teams treat the first as the definition of review and the other two as side effects.
What is interesting is that the data points the other way. Alberto Bacchelli and Christian Bird, in a 2013 study conducted at Microsoft, reported that while developers and managers name defect finding as the top purpose of review, the output actually observed looks different. The majority of real comments were not defect reports but readability improvements, alternative approaches, and shared understanding of why the code looks the way it does. The 2018 Google case study from Caitlin Sadowski and colleagues reached a similar conclusion. Inside Google, the motivation perceived as primary sits closer to education and norm maintenance than to hunting bugs.
The second purpose, knowledge transfer, works like this. A reviewer reading code acquires a map of that domain. Six months later, when that code blows up at three in the morning, having even one person around who reviewed it changes the recovery time. Seen this way, staffing a review with only "the person who knows it best" is not an optimization. One person who knows it well plus one person who does not is often the better trade for the team as a whole.
Third, shared ownership shows up when something goes wrong. Code that merged through review is a decision of the team rather than the property of its author. This is not a device for dodging responsibility; it is the precondition for treating failure as a systems problem rather than an individual mistake. Why a culture that stops blaming individuals is a condition for resilience is covered in turning what happened into an asset.
The Bar for Approval — Not Perfect, Clearly Better
The most common route to review hell is a reviewer asking themselves the wrong question. Ask "is this the code I would have written?" and approval never arrives.
The engineering practices document Google published sorts this out explicitly. The gist is this: a reviewer should not demand that code be perfect, and should approve when the change clearly improves the overall code health of the system. Attached to it is the line that there is no such thing as perfect code, only better code. The approval question is not whether this is the best possible version, but whether it is better than what was there before the merge.
Why this bar matters is that the cost of the opposite bar is hard to see. Under a reviewer who demands perfection, people respond in two ways, and neither is splitting PRs smaller. They start working in ways that avoid review altogether. They defer refactoring, they drop that reviewer from the reviewer list, and they look for grounds to skip approval on the excuse that something is urgent. Push review quality to the extreme and review itself gets routed around.
There are exceptions to the bar, though. Security, data integrity, migrations that are hard to reverse, the contract of a public API — "it got better, so it passes" does not hold in those areas. Agreeing as a team, in advance, on the list of what blocks and what goes through keeps that judgment from being misread as one reviewer being difficult.
The Grammar of a Comment — Severity Markers and a Three-Part Sentence
The most common reason a review hurts is not the content but the severity failing to travel. The reviewer writes something at the level of "would be nice, but fine either way," and the author reads it as "fix this or I will not approve." Attach ten comments and all ten get read at maximum severity.
The fix is startlingly simple: add a prefix. Google's review documentation spells out the convention of prefixing minor points with "nit:". Adapt the wording to your team; three levels are enough.
| Prefix | Meaning | Blocks approval | Example |
|---|---|---|---|
| nit | A small point, closer to taste | No | nit: making this variable name plural would read a little easier |
| suggestion | Nice to fix now, but a follow-up PR is fine | No | suggestion: pulling this branching logic into its own function would make it easier to test |
| must | Needs to be resolved before merge | Yes | must: this query is missing the user ID filter, so data from other accounts is exposed |
| question | A request for information, not a judgment | No | question: was there a reason for setting retries to three here |
The real change a prefix creates is on the reviewer's side too. To attach a severity, a reviewer has to ask themselves whether this genuinely blocks a merge, and that question alone visibly reduces the number of blocking comments.
Once severity is set, the next thing is sentence structure. Write in the order observation, impact, suggestion and the judgment words drop out on their own. "This code is bad" has no observation, no impact, and no suggestion; it is pure verdict. Whereas "this function is swallowing the exception (observation), which will make tracing the cause during an incident hard (impact). What about logging it and rethrowing (suggestion)?" turns the same content into collaboration. This structure is not specific to code review; it is general feedback grammar. Sentence patterns for particular situations are collected in workplace phrases that work.
One thing to add: a comment naming what went well is information, not politeness. If only negative signal flows through review, the author has no way to know what to keep. A single line like "I would like to take this error handling approach into the other modules too" is how a team convention gets made.
Designing the PR for the Reviewer — Size and Description
A large share of review quality is determined by the author, not the reviewer. And the biggest variable the author holds is size.
There is a widely cited number: past 200 to 400 lines at a time, defect-finding ability drops. The source is a 2006 case study SmartBear ran at Cisco. Analyzing data from about 50 developers, 3.2 million lines of code, and 2,500 reviews, it concluded that defects found per line fall as review size grows, and that effectiveness drops sharply above a pace of 500 lines an hour or beyond 60 minutes of continuous review.
It is worth knowing the limits of that evidence too. It is a single case study from one company, one codebase, and the tooling environment of 2006, and it assumes practices that predate the GitHub pull request. On top of that, the headline metric, defect density, is divided by line count, so a lower value on a large change is partly true by definition. That it counted only the defects reviewers actually reported is another catch. So 200 to 400 lines is more accurately taken as a rule of thumb than a law of physics.
Even as a rule of thumb, the direction is broadly right. For reference, the median change size reported in the Google case study was in the tens of lines. Far smaller than we imagine. In practice this much is enough: do not mix refactoring and behavior change in one PR, commit automated formatting separately from real logic changes, and for large changes merge the interface first and attach the implementation behind it.
Once size is handled, the other half is the description. A good PR description carries three things: why this is happening (context and links), what options were considered and why this one was chosen (alternatives), and where you want attention (the review request). The third is what decisively cuts round trips in asynchronous review. One line saying "please look especially at the lock handling in file three, the rest is mechanical" saves the reviewer two hours and saves the team a day of round trips.
The Craft of Being Reviewed
Review culture is not built by reviewers alone. There is craft on the receiving end too.
First, delay your first reaction. Reading a comment and immediately writing a rebuttal is the most common mistake. The more time you spent on the code, the more a note reads like a personal attack. Read it again thirty minutes later and the same sentence often looks different.
Second, when you disagree, ask for the reasoning. "That would probably hurt performance" reaches a conclusion far slower than "I picked this approach because of A, so what does your suggestion buy us on that dimension?" If the argument drags, changing the format is also an option. Past three round trips, a fifteen-minute call is cheaper than twenty messages in a thread.
Third, reply even to the things you decided not to do. A comment passed over in silence reads to the reviewer as being ignored. One line does it: "I will handle this in a follow-up PR, the ticket is filed" or "this behavior is intentional, I added a comment explaining it." Response rate builds trust more than adoption rate does.
Fourth, say out loud what you learned. "I did not know this pattern, that is useful" is not flattery; it is feedback to the reviewer. Review is generally poorly rewarded labor, and that one line changes how carefully the next one gets done.
When Review Becomes a Power Game
Everything so far assumes the healthy case. The unhealthy case exists too.
The signs tend to look alike. Only one particular reviewer takes forever on your PRs. The nature of the objections changes review to review, so there is no target to hit. Architectural problems slide past while variable names and whitespace collect twenty comments. That last item has exactly the structure C. Northcote Parkinson pointed out in 1957 with the bike shed. Nobody can offer an opinion on the reactor design, but everybody has one about the color of the bike shed. Where trivial comments cluster is usually a signal that nobody read the hard part.
There is also empirical work suggesting review is not a perfectly neutral technical act. The 2017 analysis of GitHub pull requests by Josh Terrell and colleagues reported that women had a higher acceptance rate overall, but that the advantage disappeared for outside contributors whose gender was identifiable from their profile. The study drew substantial pushback over its gender inference method and over causal readings of observational data, and the question is not settled. But not being settled is not evidence for "review is purely technical" either.
The response works better as process than as personal skill. Let formatters and linters, not people, rule on style arguments. Nail severity prefixes into team rules so blocking authority is explicit. Track time-to-approval and round-trip counts as team metrics rather than per-person ones. And if a different standard repeatedly applies to one particular person, that is not a problem to solve in a review thread; it is a problem for a one-on-one with your manager.
Closing — Review Passes Through People, Not Code
The measure of a good review culture is not the number of defects found. It is how many more people can fix that code six months from now. Defects also get caught by tests and monitoring, but review is very nearly the only channel through which design context spreads across a team.
There are only two things you can change today: put a severity prefix on your next comment, and put one line in your next PR description saying where to look. Culture is not made of manifestos; it is made of lines like these stacking up.