Skip to content

필사 모드: 코드 리뷰의 대화법: 갈등 없이 피드백 주고받기

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

들어가며 — 리뷰는 코드가 아니라 사람 사이에서 일어난다

코드 리뷰는 표면적으로는 기술 활동입니다. 버그를 찾고, 설계를 검증하고, 스타일을 맞춥니다. 하지만 조금만 오래 팀에서 일해 본 사람은 압니다. 리뷰가 무너지는 이유는 대개 기술이 아니라 사람 때문이라는 것을요. 완벽하게 정확한 지적이 관계를 망치고, 사소한 코멘트 하나가 사흘짜리 감정 싸움으로 번집니다. 리뷰 요청이 며칠씩 방치되어 작성자의 의욕을 꺾기도 합니다.

그래서 이 글의 전제는 단순합니다. 코드 리뷰는 근본적으로 커뮤니케이션이다. 코드는 매개일 뿐이고, 실제로 오가는 것은 두 엔지니어의 판단, 의도, 그리고 신뢰입니다. 이 관점을 받아들이면 리뷰의 많은 문제가 다르게 보입니다. "이 지적이 맞느냐"만큼이나 "이 지적이 어떻게 전달되느냐"가 중요해집니다.

이 글에서는 마찰 없이 피드백을 주고받는 구체적인 방법을 다룹니다. 사람이 아니라 코드를 리뷰하는 프레이밍, 코멘트에 라벨을 붙이는 컨벤셔널 코멘트, 명령 대신 질문하기, 작은 PR의 힘, 칭찬의 역할, 작성자로서 피드백을 받는 태도, 그리고 리뷰어와 작성자가 각자 지는 책임까지 순서대로 살펴보겠습니다.

사람이 아니라 코드를 리뷰하라

리뷰에서 가장 근본적인 원칙은 비판의 대상을 사람이 아니라 코드로 둔다는 것입니다. 이것은 예의의 문제가 아니라 정확성의 문제입니다. 리뷰의 목적은 코드베이스를 개선하는 것이지 작성자를 평가하는 것이 아니기 때문입니다.

같은 지적도 프레이밍에 따라 완전히 다르게 들립니다.

  • "당신은 여기서 에러 처리를 빠뜨렸어요." → 작성자를 향한 화살입니다. 방어적으로 만듭니다.
  • "이 경로에서 에러 처리가 빠진 것 같은데, 확인해 볼까요?" → 코드를 향합니다. 함께 들여다보자는 초대입니다.

핵심 도구는 주어를 바꾸는 것입니다. "당신(you)"을 "우리(we)" 또는 "이 코드"로 바꾸세요.

  • "왜 이렇게 짰어요?" 대신 → "이 부분을 이렇게 한 이유가 궁금해요."
  • "당신 함수가 너무 길어요." 대신 → "이 함수를 좀 쪼개면 읽기 쉬울 것 같아요."
  • "당신이 버그를 넣었어요." 대신 → "이 코드가 빈 리스트에서 예외를 던질 것 같아요."

우리는 같은 편에서 코드라는 문제를 함께 바라보는 것이지, 마주 서서 서로를 겨누는 것이 아닙니다.

이 "우리" 프레이밍은 단순한 완곡어법이 아닙니다. 그것은 실제로 참인 무언가를 반영합니다. 병합된 코드는 팀 전체의 것이 됩니다. 그 버그는 곧 우리 모두의 온콜 호출이 됩니다. 그러니 "우리"는 정직한 표현입니다. 작성자와 리뷰어는 코드 품질이라는 공동 목표를 향해 함께 일하는 협력자입니다.

Nit과 blocker — 컨벤셔널 코멘트

리뷰에서 흔한 오해 하나는 모든 코멘트가 같은 무게를 가진다고 착각하는 것입니다. 작성자 입장에서는 변수명 취향 같은 사소한 제안과, 데이터 유실을 일으키는 심각한 결함이 똑같은 크기의 코멘트 상자로 나란히 달립니다. 어느 것이 반드시 고쳐야 하는 것이고 어느 것이 무시해도 되는 것인지 알 수가 없습니다. 이 모호함이 마찰의 큰 원인입니다.

컨벤셔널 코멘트(Conventional Comments) 는 이 문제를 라벨로 해결합니다. 모든 리뷰 코멘트 앞에 그 코멘트의 의도와 심각도를 나타내는 라벨을 붙이는 간단한 관례입니다. 대표적인 라벨은 이렇습니다.

  • nit: 아주 사소한 지적. 고치면 좋지만 안 고쳐도 병합에 지장 없음. (예: 사소한 스타일)
  • suggestion: 개선 제안. 작성자가 받아들일지 판단하면 됨.
  • question: 진짜 질문. 이해가 안 되거나 의도를 확인하고 싶을 때.
  • blocker: (또는 issue) 반드시 해결해야 병합 가능한 문제.
  • praise: 칭찬. 잘된 부분을 명시적으로 짚어 줌.
  • nitpick / thought / typo 등 팀에 따라 변형도 씁니다.

라벨이 붙은 코멘트는 이렇게 생겼습니다.

nit: 여기는 let 대신 const가 더 명확할 것 같아요.

question: 이 리스트가 비어 있을 때는 어떻게 동작하나요?

blocker: 이 경로에서 파일 핸들이 닫히지 않고 새는 것 같습니다.

suggestion: 이 조건을 early return으로 빼면 중첩이 줄어들 것 같아요.

praise: 여기서 early return으로 처리한 거 정말 깔끔하네요.

라벨 하나가 만들어 내는 차이는 큽니다.

  1. 작성자가 우선순위를 안다. blocker와 nit이 명확히 구분되니, 무엇부터 처리할지, 무엇은 취향껏 결정할지 즉시 판단됩니다.
  2. 리뷰어의 톤이 부드러워진다. "nit:"이라는 두 글자가 "이건 사소한 거니 부담 갖지 마세요"라는 신호를 자동으로 실어 줍니다.
  3. 논쟁이 줄어든다. nit으로 표시된 제안을 작성자가 넘겨도 아무도 기분 상하지 않습니다. 애초에 "이건 필수가 아니다"라고 합의된 것이니까요.

컨벤셔널 코멘트에는 라벨 외에 데코레이터라는 선택적 장치도 있습니다. 예를 들어 (non-blocking)처럼 괄호로 추가 맥락을 붙입니다. suggestion (non-blocking): ... 처럼요. 굳이 격식을 갖추지 않아도, 팀이 "nit은 무시해도 된다"는 공유된 이해만 가져도 리뷰 문화는 눈에 띄게 달라집니다.

명령이 아니라 질문으로

같은 지적을 전달하는 방식 중에서 가장 강력한 전환은 명령을 질문으로 바꾸는 것입니다. 이유는 두 가지입니다. 하나는 인간적인 이유이고, 다른 하나는 기술적인 이유입니다.

인간적인 이유부터. 명령은 작성자를 방어적으로 만듭니다. "이건 틀렸어요(This is wrong)"라는 말은 판결처럼 들립니다. 작성자는 반박하거나 위축되거나 둘 중 하나로 반응합니다. 반면 "x가 null이면 어떻게 되나요?(What happens if x is null?)"라는 질문은 작성자를 생각하게 만듭니다. 방어 대신 탐구가 시작됩니다.

기술적인 이유도 있습니다. 당신이 틀렸을 수도 있습니다. 리뷰어는 작성자보다 맥락을 적게 가지고 있는 경우가 많습니다. "이건 버그예요"라고 단정했는데 사실 위쪽 어딘가에서 이미 방어 코드가 있었다면, 리뷰어가 무안해집니다. 질문 형태는 이 위험을 자연스럽게 회피합니다. 질문은 "내가 놓친 게 있을 수 있다"는 겸손을 내포하기 때문입니다.

명령을 질문으로 바꾸는 몇 가지 예시입니다.

  • "이거 null 체크 빠졌어요." → "이 값이 null로 들어오는 경우는 없을까요?"
  • "여기 락을 걸어야 해요." → "여러 스레드가 동시에 이걸 건드리면 어떻게 될까요?"
  • "이 쿼리는 느려요." → "이 테이블이 수백만 행으로 커지면 이 쿼리는 어떤 인덱스를 타게 될까요?"
  • "이 로직 이상해요." → "이 분기가 정확히 어떤 케이스를 처리하는지 설명해 주실 수 있어요?"

다만 균형이 필요합니다. 모든 것을 질문으로 포장할 필요는 없습니다. 명백한 사실(예: 오타, 문법 오류)이나 확실한 blocker는 질문으로 돌려 말하면 오히려 수동공격처럼 느껴지거나 시간을 낭비합니다. "이 반복문은 마지막 원소를 건너뜁니다(off-by-one)"처럼 확실한 것은 명확하게 말하는 편이 낫습니다. 질문은 불확실할 때, 그리고 작성자의 사고를 유도하고 싶을 때 쓰는 도구입니다.

또 하나, 진짜 질문과 가짜 질문을 구분해야 합니다. "이걸 왜 이렇게 했죠?"가 사실은 "이건 틀렸어요"의 우회 표현이라면, 작성자는 금세 눈치챕니다. 그런 위장된 비난은 솔직한 지적보다 더 나쁩니다. 질문을 할 거면 정말 답을 궁금해하세요. 정말 문제라고 확신한다면 질문으로 위장하지 말고 blocker로 명확히 표시하세요.

작은 PR — 큰 PR이 도장만 받는 이유

리뷰 품질을 좌우하는 가장 강력한 단일 요인은 놀랍게도 커뮤니케이션 기법이 아니라 PR의 크기입니다. 이것은 인간 심리와 직결됩니다.

800줄짜리 PR을 받았다고 상상해 봅시다. 리뷰어는 무의식적으로 압도됩니다. 이 큰 덩어리를 한 줄 한 줄 정직하게 검토하려면 한 시간이 넘게 걸립니다. 그런데 다른 일도 밀려 있습니다. 결과는 예측 가능합니다. 리뷰어는 대충 훑고 "LGTM(좋아 보여요)"을 눌러 버립니다. 이것이 러버 스탬프(rubber-stamping, 도장 찍기) 입니다. 리뷰가 형식적으로만 이루어지고 실질적 검토는 일어나지 않는 것입니다.

연구와 경험이 일관되게 말하는 바는 이렇습니다.

  • 큰 PR은 결함 발견율이 낮습니다. 리뷰어의 집중력은 유한한데, 큰 PR은 그 집중력을 얇게 펴 버립니다. 200줄을 넘어가면 결함 발견율이 뚝 떨어진다는 관찰이 흔합니다.
  • 작은 PR은 진짜 리뷰를 받습니다. 50줄짜리 변경은 리뷰어가 통째로 머릿속에 담고 각 줄을 실제로 생각할 수 있습니다.
  • 작은 PR은 빨리 병합됩니다. 리뷰가 부담스럽지 않으니 방치되지 않고, 피드백 왕복도 빠릅니다.

그래서 좋은 작성자는 변경을 작고 응집력 있는 단위로 쪼갭니다. 하나의 PR은 하나의 논리적 변경을 담는 것이 이상적입니다. 리팩터링과 기능 추가를 한 PR에 섞지 마세요. 리뷰어가 "이 줄은 리팩터링인가 새 동작인가"를 매번 구분해야 하는 순간, 리뷰는 급격히 어려워집니다.

큰 변경이 불가피할 때는 몇 가지 전략이 있습니다.

  • 논리적 커밋으로 쪼개기. 리뷰어가 커밋 단위로 따라올 수 있게 히스토리를 정리합니다. 이런 커밋 분할과 브랜치 전략은 실제 저장소에서 연습해 보는 것이 가장 빠릅니다. 깃 플레이그라운드에서 브랜치를 나누고 커밋을 재구성하는 워크플로를 손에 익혀 두면 큰 변경을 다룰 때 훨씬 수월합니다.
  • 스택형 PR(stacked PRs). 큰 기능을 의존 관계가 있는 여러 개의 작은 PR로 쌓아 올립니다. 각 PR은 독립적으로 리뷰 가능합니다.
  • 순수 이동은 따로. 코드를 단순히 옮기기만 하는 변경(이동, 이름 변경)은 로직 변경과 분리해 별도 PR로 만듭니다. 그래야 diff가 깨끗해집니다.

잘한 코드를 칭찬하라

대부분의 리뷰는 문제만 지적합니다. 리뷰어는 무의식적으로 결함을 찾는 사냥꾼 모드에 들어가고, 잘된 부분은 조용히 지나칩니다. 하지만 이것은 놓친 기회이자, 리뷰 문화를 갉아먹는 습관입니다.

칭찬이 중요한 이유는 여러 가지입니다.

  1. 리뷰가 순전히 부정적 경험이 되는 것을 막습니다. 열 개의 지적 사이에 진심 어린 칭찬 하나가 있으면, 작성자는 리뷰를 공격이 아니라 대화로 느낍니다. 이것은 심리적 안전감의 문제입니다.
  2. 좋은 패턴을 강화합니다. 어떤 접근이 훌륭했는지 명시적으로 짚어 주면, 작성자와 팀은 그 패턴을 반복하게 됩니다. 리뷰는 결함 필터일 뿐 아니라 학습의 장입니다.
  3. 신뢰를 쌓습니다. 리뷰어가 좋은 것을 알아봐 준다는 걸 알면, 작성자는 그 리뷰어의 비판도 더 진지하게 받아들입니다.

칭찬은 구체적일 때 힘이 있습니다. "잘했어요"는 공허합니다. "이 에러 케이스를 이렇게 미리 처리한 게 정말 좋네요, 나중에 디버깅할 사람을 살렸어요"는 무엇이 왜 좋은지를 알려 줍니다.

praise: 이 재귀를 반복문으로 바꾼 거 좋네요, 스택 오버플로 걱정이 사라졌어요.

praise: 테스트 케이스에 경계값을 꼼꼼히 넣어 주셔서 리뷰하기 편했어요.

praise: 이 주석 하나가 왜 이렇게 짰는지를 완벽히 설명해 주네요. 고마워요.

주의할 점은 가식적인 칭찬은 오히려 역효과라는 것입니다. 모든 PR에 기계적으로 칭찬을 넣으면 그 무게가 사라집니다. 진짜로 잘된 것을 봤을 때, 그때 진심으로 짚어 주세요. 그 진정성이 칭찬을 살립니다.

피드백을 우아하게 받기 — 작성자의 자세

지금까지는 주로 리뷰어의 태도를 다뤘습니다. 하지만 리뷰는 양방향입니다. 피드백을 받는 쪽의 태도 역시 리뷰 문화를 좌우합니다. 그리고 솔직히 말하면, 이쪽이 훨씬 더 어렵습니다. 내 코드가 지적받을 때 방어적으로 반응하지 않기란 본능을 거스르는 일이기 때문입니다.

핵심 원칙 몇 가지입니다.

1. 자아와 코드를 분리하라. 이것이 가장 중요합니다. 당신의 코드는 당신이 아닙니다. 코드에 대한 비판은 당신이라는 사람에 대한 비판이 아닙니다. 이 분리가 안 되면 모든 리뷰가 인신공격처럼 느껴집니다. 반대로 이 분리가 되면, 지적은 그저 코드를 더 낫게 만드는 정보가 됩니다. 숙련된 엔지니어일수록 자기 코드가 지적받는 것을 위협이 아니라 공짜 개선 기회로 받아들입니다.

2. 선의를 가정하라. 리뷰어의 코멘트가 무뚝뚝하거나 톤이 애매할 때, 최악의 의도를 가정하지 마세요. 대부분의 지적은 코드를 개선하려는 선의에서 나옵니다. 텍스트는 톤을 잃기 쉽고, 리뷰어가 바빠서 짧게 썼을 뿐일 수 있습니다. "이 사람이 나를 무시하나?"가 아니라 "이 사람이 무엇을 걱정하는 걸까?"로 읽으세요.

3. 리뷰어에게 감사하라. 리뷰는 시간과 에너지가 드는 일입니다. 누군가 당신의 코드를 꼼꼼히 봐 주었다는 것은 선물입니다. 특히 좋은 버그를 잡아 줬을 때는 명시적으로 고마움을 표현하세요. "좋은 지적이에요, 놓칠 뻔했네요" 같은 한마디가 리뷰어에게 큰 힘이 됩니다.

4. 동의하지 않으면 대화하라, 그러나 우아하게. 모든 지적을 받아들일 필요는 없습니다. 리뷰어가 틀렸을 수도, 맥락을 놓쳤을 수도 있습니다. 그럴 때는 방어가 아니라 설명으로 대응하세요. "그건 틀렸어요"가 아니라 "그렇게 볼 수도 있는데, 저는 이런 이유로 이렇게 했어요. 어떻게 생각하세요?"라고요. 그리고 정말 합의가 안 되면, 코멘트 스레드에서 무한히 다투지 말고 화상 통화나 직접 대화로 옮기세요. 텍스트는 대립을 증폭시키지만 목소리는 그것을 누그러뜨립니다.

5. 모든 코멘트에 응답하라. 지적을 반영했든, 반영하지 않기로 했든, 각 코멘트에 짧게라도 반응을 남기세요. 침묵은 리뷰어를 불안하게 하고, 무시당했다는 느낌을 줍니다. "고쳤어요" 또는 "이런 이유로 그대로 두겠습니다"라는 한 줄이 스레드를 깔끔하게 닫습니다.

방어는 당신의 코드를 지키지만, 열린 태도는 당신의 성장을 지킵니다.

리뷰어의 책임과 작성자의 책임 — 양방향 계약

좋은 리뷰 문화는 한쪽의 노력만으로 만들어지지 않습니다. 그것은 리뷰어와 작성자가 각자의 책임을 성실히 지는 양방향 계약입니다. 한쪽이 계약을 어기면 다른 쪽의 노력도 무너집니다.

두 역할의 책임을 나란히 놓으면 이렇습니다.

리뷰어의 책임작성자의 책임
빠르게 응답한다 (리뷰를 방치하지 않기)리뷰하기 쉬운 크기로 PR을 만든다
코드를 사람과 분리해 비판한다자아를 코드와 분리해 피드백을 받는다
blocker와 nit을 명확히 구분한다좋은 설명(무엇을, 왜)을 PR에 쓴다
왜 그 지적을 하는지 이유를 설명한다모든 코멘트에 응답한다
잘된 부분도 짚어 준다선의를 가정하고 감사한다
자기 취향과 실제 문제를 구분한다리뷰어의 시간을 존중한다 (셀프 리뷰 먼저)

이 표에서 특히 강조할 몇 가지가 있습니다.

리뷰 지연(latency)은 조용한 독입니다. 리뷰가 며칠씩 방치되면 작성자는 컨텍스트를 잃고, 브랜치는 낡고, 병합 충돌이 쌓이고, 무엇보다 의욕이 꺾입니다. 좋은 팀은 리뷰를 우선순위 높은 일로 취급합니다. "완벽한 리뷰를 내일"보다 "충분히 좋은 리뷰를 오늘"이 대개 더 낫습니다. 리뷰 요청은 다른 사람의 진행을 막고 있는(blocking) 일이라는 점을 기억하세요.

작성자는 리뷰어의 일을 덜어 줄 책임이 있습니다. 좋은 PR 설명은 리뷰의 절반입니다. 이 변경이 무엇을 하는지, 필요한지, 어떻게 테스트했는지, 리뷰어가 특히 주의 깊게 봐야 할 곳은 어디인지를 적어 주세요. 그리고 리뷰를 요청하기 전에 스스로 자기 diff를 먼저 읽으세요. 남겨진 디버그 출력, 주석 처리된 코드, 오타를 작성자가 먼저 잡으면 리뷰어의 시간을 아낄 수 있습니다. 셀프 리뷰는 최소한의 예의입니다.

리뷰어는 취향과 문제를 구분할 책임이 있습니다. "나라면 이렇게 안 짰을 텐데"는 대개 문제가 아니라 취향입니다. 코드가 정확하고 명료하다면, 리뷰어의 개인 스타일과 다르다는 이유만으로 변경을 요구해서는 안 됩니다. 정말 중요한 것(정확성, 가독성, 유지보수성)에 에너지를 쓰고, 취향 차이는 nit으로 가볍게 남기거나 아예 넘기세요.

이 계약이 지켜질 때, 리뷰는 관문이 아니라 협업이 됩니다. 두 사람이 각자의 역할을 신뢰하며 같은 목표를 향해 일하는 것입니다.

마치며

코드 리뷰를 커뮤니케이션으로 바라보면, 마찰의 원인 대부분이 기술이 아니라 전달 방식에 있다는 것이 드러납니다. 사람이 아니라 코드를 겨누고, "당신" 대신 "우리"를 쓰고, nit과 blocker를 라벨로 구분하고, 명령 대신 질문하고, 변경을 작게 쪼개고, 잘된 것을 칭찬하는 것. 이 모든 기법의 공통점은 상대를 협력자로 대한다는 것입니다.

그리고 리뷰는 양방향입니다. 작성자는 자아와 코드를 분리하고, 선의를 가정하고, 리뷰어에게 감사하고, 리뷰하기 쉬운 PR을 만들 책임이 있습니다. 리뷰어는 빠르게 응답하고, 이유를 설명하고, 취향과 문제를 구분할 책임이 있습니다. 이 계약을 양쪽이 지킬 때, 리뷰는 서로를 겨루는 자리가 아니라 함께 코드를 더 낫게 만드는 자리가 됩니다.

기술적으로 완벽한 리뷰가 관계를 망칠 수 있고, 다정하지만 허술한 리뷰가 버그를 통과시킬 수 있습니다. 좋은 리뷰는 이 둘을 모두 잡습니다. 정확하면서도 친절하고, 엄격하면서도 존중하는 것. 그것이 마찰 없는 피드백의 본질입니다.

참고 자료

현재 단락 (1/93)

코드 리뷰는 표면적으로는 기술 활동입니다. 버그를 찾고, 설계를 검증하고, 스타일을 맞춥니다. 하지만 조금만 오래 팀에서 일해 본 사람은 압니다. 리뷰가 무너지는 이유는 대개 기술...

작성 글자: 0원문 글자: 7,324작성 단락: 0/93