인간답게 코드 리뷰하는 방법 (2부)
원문은 Michael Lynch님이 에 게재했습니다. 이 블로그 구독하기
이 글은 코드 리뷰에서 효과적으로 소통하고 함정을 피하는 방법에 대한 글의 후반부입니다. 여기서는 보기 흉한 갈등을 피하면서 코드 리뷰를 성공적으로 마무리하는 기법에 집중합니다.
기초적인 내용은 1부에서 다뤘으니 먼저 그쪽을 읽어보시길 권합니다. 급하시다면 요약은 이렇습니다. 좋은 코드 리뷰어는 버그를 찾는 데 그치지 않고 동료가 성장할 수 있도록 성의 있는 피드백을 제공합니다.
내 생애 최악의 코드 리뷰
내 생애 최악의 코드 리뷰는 말로리라 부를 전 동료와의 리뷰였습니다. 그녀는 내가 입사하기 몇 년 전부터 회사에 다녔지만 우리 팀으로 옮겨온 지는 얼마 되지 않았습니다.
리뷰
말로리가 첫 번째 체인지리스트를 리뷰로 보냈을 때 코드는 다소 조악했습니다. 그녀는 Python을 한 번도 써본 적이 없었고, 내가 유지보수하던 투박한 레거시 시스템 위에 코드를 쌓고 있었습니다.
나는 발견한 문제들을 하나하나 성실하게 기록했고, 총 59개였습니다. 내가 읽었던 리뷰 관련 글들에 따르면 나는 훌륭한 일을 해낸 셈이었습니다. 이렇게나 많은 실수를 찾아냈으니 분명 나는 좋은 리뷰어임에 틀림없었습니다.
며칠 뒤 말로리는 수정된 체인지리스트와 내 코멘트에 대한 답변을 보내왔습니다. 그녀는 오타나 변수명 변경 같은 간단한 문제들은 고쳤지만, 더 상위 수준의 문제들은 해결을 거부했습니다. 예를 들어 잘못된 입력에 대해 코드가 정의되지 않은 동작을 한다거나, 함수 하나가 제어 흐름을 6단계나 중첩하고 있다는 지적이었습니다. 대신 그녀는 그런 문제들을 고치는 데 공을 들일 가치가 없다는 식으로 퉁명스럽게 답했습니다.
화가 나고 답답해진 나는 다시 코멘트를 남겼습니다. 어조는 겉으로는 정중했지만 수동 공격적인 영역으로 넘어가고 있었습니다. “잘못된 입력에 대해 정의되지 않은 동작을 유지하려는 이유를 설명해 주시겠어요?” 짐작하시겠지만, 말로리의 답변은 한층 더 완고해졌습니다.
악순환
일주일이 지난 화요일이었습니다. 말로리와 나는 여전히 같은 리뷰를 두고 공방을 벌이고 있었습니다. 전날 저녁 나는 그녀가 퇴근한 뒤에야 최신 코멘트를 보냈습니다. 그녀가 내 글을 읽는 자리에 함께 있고 싶지 않았기 때문입니다.
아침 내내 다음 리뷰 라운드를 앞두고 가슴이 내려앉는 기분이었습니다. 점심에서 돌아오니 말로리는 자리에는 없었지만 새로운 변경 사항을 보내놓았습니다. 아마 그녀도 내가 답을 읽는 순간 곁에 있고 싶지 않았던 것 같습니다.
그녀의 답변을 하나씩 읽을 때마다 가슴이 쿵쾅거렸고 분노는 더 커졌습니다. 나는 즉시 키보드를 두드리며 반박을 쓰기 시작했고, 그녀가 제안한 수정 사항을 반영하지도, 내가 승인할 만한 근거를 제시하지도 않았다고 지적했습니다.
우리는 매일 같은 과정을 반복했고, 3주 동안 코드는 거의 달라지지 않았습니다.
중재
다행히 가장 연차가 높은 동료 밥이 이 악순환을 끊어주었습니다. 그는 긴 휴가에서 돌아와 우리가 서로에게 날 선 리뷰 코멘트를 던지고 있는 모습을 보고 깜짝 놀랐습니다. 그는 상황을 즉시 교착 상태로 진단했습니다. 그는 리뷰를 자신이 이어받겠다고 요청했고, 우리는 둘 다 동의했습니다.
밥은 말로리에게 체인지리스트를 쪼개 새로 만들라고 요청하는 것으로 리뷰를 시작했습니다. 우리가 크게 다투지 않았던 두 개의 작은 라이브러리를 각각 30~50줄 규모로 분리한 것이었습니다. 말로리가 그렇게 하자 밥은 즉시 승인했습니다.
이어서 밥은 약 200줄로 줄어든 메인 체인지리스트로 돌아왔습니다. 그는 몇 가지 사소한 제안을 남겼고, 말로리는 이를 반영했습니다. 그리고 밥은 체인지리스트를 승인했습니다.
밥의 리뷰 전체는 이틀 만에 끝났습니다.
소통이 중요하다
눈치채셨겠지만 이 갈등은 사실 코드 자체의 문제가 아니었습니다. 코드에 분명 문제가 있었지만, 효과적으로 소통할 수 있는 동료들이라면 충분히 해결 가능한 수준이었습니다.
불쾌한 경험이었지만 돌이켜보면 겪길 잘했다고 생각합니다. 덕분에 리뷰 방식을 재점검하고 개선할 점을 찾을 수 있었습니다.
아래에서는 비슷한 불쾌한 결과를 겪을 위험을 줄여줄 기법들을 공유합니다. 뒤에서 말로리 사례로 돌아와 왜 나의 접근이 거꾸로였고 밥의 접근이 조용히 훌륭했는지 설명하겠습니다.
기법
- 코드를 한두 등급 끌어올리는 것을 목표로 하라
- 반복되는 패턴에 대한 피드백은 제한하라
- 리뷰의 범위를 존중하라
- 큰 리뷰를 쪼갤 기회를 찾아라
- 진심 어린 칭찬을 하라
- 남은 수정이 사소하면 승인하라
- 교착 상태를 선제적으로 처리하라
코드를 한두 등급 끌어올리는 것을 목표로 하라
동료가 이론적으로는 코드를 개선할 모든 기회를 탐색하고 싶어 할지 몰라도, 인내심에는 한계가 있습니다. 체인지리스트를 다듬을 새로운 기발한 방법을 계속 떠올리며 라운드마다 승인을 미루면 동료는 금세 지쳐버릴 것입니다.
나는 코드를 A부터 F까지 학점으로 매긴다고 생각합니다. D 학점으로 시작하는 체인지리스트를 받으면 C나 B-까지 끌어올리려 노력합니다. 완벽하진 않지만 충분히 괜찮은 수준입니다.
이론적으로는 D를 A+까지 끌어올리는 것도 가능하지만, 그러려면 족히 여덟 라운드 이상의 리뷰가 필요할 것입니다. 그 과정이 끝나면 작성자는 당신을 싫어하게 되고 다시는 코드를 보내고 싶어 하지 않을 것입니다.
“C 등급 코드를 받아들이면 결국 코드베이스 전체가 C 등급이 되는 것 아닌가?”라고 생각할 수도 있습니다. 다행히 그렇지 않습니다. 내 경험상 동료를 도와 D를 C로 끌어올리면, 다음에 받는 체인지리스트는 C에서 시작합니다. 몇 달이 지나면 처음부터 B로 시작하는 리뷰를 받게 되고, 리뷰가 끝나면 A가 됩니다.
F는 기능적으로 틀렸거나 너무 복잡해서 정확성을 확신할 수 없는 코드에만 해당합니다. 몇 라운드 리뷰를 거친 뒤에도 코드가 여전히 F라면 그때만 승인을 보류해야 합니다. 아래 교착 상태 섹션을 참고하세요.
반복되는 패턴에 대한 피드백은 제한하라
작성자의 실수 여러 개가 같은 패턴에 해당한다는 걸 알게 되면 모든 경우를 하나하나 지적하지 마세요. 같은 코멘트를 25번 쓰는 데 시간을 쓰고 싶은 사람도 없고, 작성자도 중복된 코멘트 25개를 읽고 싶어 하지 않습니다.
패턴의 예시는 두세 개 정도만 짚어도 충분합니다. 그 이상이라면 개별 사례마다 지적하기보다 패턴 자체를 고쳐달라고 요청하세요.
리뷰의 범위를 존중하라
리뷰어가 체인지리스트에 포함된 코드 인근의 무언가를 발견하고 작성자에게 고쳐달라고 요구하는 안티패턴을 자주 봅니다. 작성자가 이를 따르고 나면 리뷰어는 코드가 개선되긴 했지만 일관성이 떨어진다고 느끼고 몇 가지 사소한 수정을 더 요구합니다. 그리고 또 몇 가지를 더 요구합니다. 그렇게 좁은 범위의 체인지리스트가 끝없이 관련 없는 변경으로 부풀어 오릅니다.
배고픈 작은 쥐가 현관에 나타나면 쿠키를 주고 싶어질지도 모릅니다. 쿠키를 주면 쥐는 우유 한 잔을 달라고 할 겁니다. 우유 거품을 닦기 위해 거울을 보고 싶어 할 테고, 그러다 머리를 다듬어야겠다며 가위를 빌려달라고 할 겁니다…
-로라 조피 누머로프, If You Give a Mouse a Cookie
경험칙은 이렇습니다. 체인지리스트가 해당 줄을 건드리지 않았다면 범위 밖입니다.
예를 들어 보겠습니다.
코드베이스에 남아 있는 매직 넘버와 말도 안 되는 변수 이름 때문에 밤잠을 설칠 것 같더라도 범위 밖입니다. 그 인근 코드를 작성한 사람이 이번 작성자와 같은 사람이라도 마찬가지입니다. 정말 심각하게 나쁘다면 버그를 등록하거나 직접 수정하세요. 다만 이번 리뷰에서 작성자에게 떠넘기지는 마세요.
예외는 체인지리스트가 실제로 해당 줄을 건드리진 않았지만 주변 코드에 영향을 미쳐 그 줄을 잘못되게 만든 경우입니다. 예를 들면 다음과 같습니다.
이 경우 작성자에게 함수명을 ValidateAndSerialize에서 단순히 Serialize로 바꾸라고 지적해야 합니다. 함수 시그니처가 있는 줄을 직접 건드리진 않았지만, 어쨌든 그 줄을 부정확하게 만들었기 때문입니다.
코멘트할 게 많지 않고 범위에서 살짝 벗어난 곳에 간단히 고칠 수 있는 문제가 보이면 나는 이 규칙을 부드럽게 어기기도 합니다. 이럴 때는 작성자가 원하면 무시해도 좋다는 점을 분명히 합니다.
큰 리뷰를 쪼갤 기회를 찾아라
400줄이 넘는 체인지리스트를 받으면 작성자에게 더 작은 단위로 쪼개달라고 요청하세요. 이 기준을 초과할수록 더 강하게 요청하세요. 나는 개인적으로 1,000줄이 넘는 체인지리스트는 리뷰를 거부합니다.
작성자는 쪼개는 작업이 귀찮다며 불평할 수도 있습니다. 논리적인 분할 경계를 찾아주며 부담을 덜어주세요. 가장 쉬운 경우는 체인지리스트가 여러 파일을 독립적으로 건드릴 때입니다. 이 경우 파일을 더 작은 묶음으로 나누어 체인지리스트를 쪼개면 됩니다. 더 어려운 경우에는 가장 낮은 추상화 계층에 있는 함수나 클래스를 찾으세요. 작성자에게 이들을 별도의 체인지리스트로 옮기도록 요청한 뒤, 첫 번째 체인지리스트가 머지된 후에 나머지 코드를 다시 살펴보세요.
코드 품질이 낮을 때는 특히 강하게 분할을 요청하세요. 품질이 낮은 코드를 리뷰하는 난이도는 크기에 따라 기하급수적으로 증가합니다. 엉성한 600줄짜리 체인지리스트 하나를 감사하는 것보다 엉성한 300줄짜리 두 개를 보는 편이 훨씬 낫습니다.
진심 어린 칭찬을 하라
대부분의 리뷰어는 코드의 잘못된 점에만 집중하지만, 리뷰는 긍정적인 행동을 강화할 수 있는 소중한 기회이기도 합니다.
예를 들어 문서 작성을 어려워하는 작성자를 리뷰하는데 명확하고 간결한 함수 주석을 발견했다고 해보세요. 잘했다고 알려주세요. 잘못했을 때만 지적하기보다 잘했을 때 알려줘야 더 빨리 성장합니다.
칭찬을 하려고 특정한 목표가 있어야 하는 것은 아닙니다. 체인지리스트에서 마음에 드는 부분을 보면 언제든 작성자에게 알려줍니다.
- “이 API는 몰랐는데 정말 유용하네요!”
- “우아한 해결책이네요. 저는 이런 방법을 전혀 생각 못 했어요.”
- “이 함수를 분리한 건 정말 좋은 생각이었어요. 훨씬 간단해졌네요.”
작성자가 주니어 개발자이거나 팀에 최근 합류했다면 리뷰 중에 긴장하거나 방어적으로 될 가능성이 큽니다. 진심 어린 칭찬은 당신이 가혹한 문지기가 아니라 든든한 동료임을 보여줘 긴장을 풀어줍니다.
남은 수정이 사소하면 승인하라
일부 리뷰어는 모든 코멘트에 대한 수정 사항을 직접 확인하기 전까지는 승인을 보류해야 한다고 착각합니다. 이는 불필요한 리뷰 라운드를 만들어 작성자와 리뷰어 모두의 시간을 낭비합니다.
다음 중 하나라도 해당하면 승인하세요.
- 더 이상 남길 코멘트가 없을 때.
- 남은 코멘트가 사소한 문제에 관한 것일 때.
- 예: 변수명 변경, 오타 수정
- 남은 코멘트가 선택 사항인 제안일 때.
- 승인이 이에 달려 있지 않다는 점을 명시적으로 표시해 동료가 오해하지 않도록 하세요.
코드 주석 끝에 마침표 하나 빠졌다고 승인을 보류하는 리뷰어를 본 적이 있습니다. 제발 그러지 마세요. 작성자에게 당신이 그들을 감독 없이는 간단한 문장부호조차 추가할 수 없는 사람으로 본다는 신호를 주는 셈입니다.
아직 해결되지 않은 코멘트가 있는데도 승인하는 데는 다소 위험이 따릅니다. 내 추산으로는 약 5%의 경우에 작성자가 마지막 라운드의 코멘트를 잘못 이해하거나 아예 놓치곤 합니다. 이를 완화하기 위해 나는 승인 후에 작성자의 변경 사항을 확인합니다. 드물게 소통이 어긋난 경우 작성자에게 후속 조치를 요청하거나 직접 체인지리스트를 만들어 수정합니다. 나머지 95%에서 불필요한 노력과 지연을 추가하는 것보다 5%의 경우에 약간의 추가 작업을 하는 편이 낫습니다.
교착 상태를 선제적으로 처리하라
코드 리뷰에서 최악의 결과는 교착 상태입니다. 즉, 당신이 추가 수정 없이는 체인지리스트에 서명하기를 거부하고, 작성자 역시 수정을 거부하는 상황입니다.
교착 상태로 향하고 있다는 신호는 다음과 같습니다.
- 논의 분위기가 점점 긴장되거나 적대적으로 변한다.
- 라운드당 코멘트 수가 줄어들지 않는다.
- 작성자가 유난히 많은 코멘트에 반발한다.
직접 대화하라
직접 만나거나 화상 채팅을 하세요. 텍스트로만 소통하면 대화 상대가 실제 사람이라는 사실을 잊기 쉽습니다. 동료가 고집이나 무능함 때문에 그런다고 상상하기 쉬워집니다. 미팅은 당신과 작성자 모두에게서 그런 착각을 깨뜨려 줄 것입니다.
설계 리뷰를 고려하라
논쟁적인 코드 리뷰는 프로세스 초반의 약점을 드러내는 신호일 수 있습니다. 설계 리뷰에서 다뤘어야 할 내용을 두고 논쟁하고 있지는 않나요? 애초에 설계 리뷰가 있었나요?
논쟁의 뿌리가 상위 수준의 설계 결정으로 거슬러 올라간다면, 코드 리뷰에 참여한 두 사람에게 맡기기보다 팀 전체의 의견을 구해야 합니다. 작성자와 대화해 설계 리뷰 형태로 논의를 팀 전체로 확장하는 것을 제안하세요.
양보하거나 에스컬레이션하라
당신과 동료가 교착 상태에서 오래 머무를수록 관계에 주는 피해는 커집니다. 다른 방법으로도 교착을 풀지 못했다면 선택지는 양보하거나 에스컬레이션하는 것뿐입니다.
그냥 변경 사항을 승인했을 때의 비용을 따져보세요. 낮은 품질의 코드를 무심코 받아들이면 품질 좋은 소프트웨어를 만들 수 없지만, 서로 격렬하게 싸워 더 이상 함께 일할 수 없게 되어도 높은 품질을 달성할 수 없습니다. 체인지리스트를 승인하면 실제로 얼마나 나빠질까요? 중요한 데이터를 파괴할 수도 있는 코드인가요? 아니면 최악의 경우 작업이 실패하고 개발자가 디버깅해야 하는 백그라운드 프로세스인가요? 후자에 가깝다면 관계를 유지하며 계속 협업할 수 있도록 그냥 양보하는 것을 고려하세요.
양보가 선택지가 아니라면 작성자와 상의해 논의를 팀 매니저나 테크 리드에게 에스컬레이션하세요. 다른 리뷰어에게 재배정하겠다고 제안하세요. 에스컬레이션 결과가 당신에게 불리하게 나오면 그 결정을 받아들이고 넘어가세요. 계속 싸움을 이어가면 상황이 길어질 뿐이고 당신을 프로답지 못하게 보이게 할 것입니다.
교착 상태에서 회복하기
지저분한 리뷰 논쟁은 코드 자체보다 관련된 사람들 사이의 관계에 관한 경우가 많습니다. 교착 상태나 그에 가까운 상태에 이르렀다면 근본적인 갈등을 해결하지 않으면 같은 패턴이 반복될 것입니다.
- 매니저와 상황을 논의하세요.
- 팀에 갈등이 있다면 매니저는 이를 알아야 합니다. 어쩌면 작성자가 원래 협업하기 어려운 사람일 수도 있습니다. 어쩌면 당신이 인지하지 못하는 방식으로 상황에 기여하고 있을 수도 있습니다. 좋은 매니저는 두 사람 모두가 이런 문제를 해결하도록 도와줄 것입니다.
- 서로에게서 잠시 거리를 두세요.
- 가능하다면 상황이 식을 때까지 몇 주간 서로에게 코드 리뷰를 보내지 마세요.
- 갈등 해결을 공부하세요.
- 나는 Crucial Conversations라는 책이 도움이 되었습니다. 그 조언이 상식처럼 들릴 수도 있지만, 논쟁의 열기에서 벗어나 갈등에 대한 자신의 접근 방식을 분석하는 것에는 큰 가치가 있습니다.
내 최악의 코드 리뷰: 다시 돌아보기
말로리와의 코드 리뷰를 기억하시나요? 왜 나의 리뷰는 3주 동안 수동 공격적인 진흙탕 싸움으로 변한 반면 밥의 리뷰는 이틀 만에 가볍게 끝났을까요?
내가 잘못한 점
그 리뷰는 말로리가 팀에서 받은 첫 리뷰였습니다. 나는 그녀가 평가받는다고 느끼거나 방어적으로 될 수 있다는 점을 고려하지 않았습니다. 방대한 양의 코멘트에 압도당하지 않도록 처음에는 상위 수준의 코멘트만으로 시작했어야 했습니다.
내 역할이 그녀의 작업을 가로막는 것이 아니라 앞으로 나아가도록 돕는 것임을 더 보여줬어야 했습니다. 코드 예시를 제공하거나 체인지리스트의 긍정적인 점을 짚어줬어야 했습니다.
나는 자존심이 리뷰에 영향을 미치도록 내버려 두었습니다. 지난 1년간 나는 이 오래된 시스템을 살려내느라 애써왔습니다. 그런데 갑자기 새로운 사람이 끼어들어 시스템을 만지작거리면서 내 우려를 진지하게 받아들이려 하지 않았던 것입니다. 나는 이를 모욕으로 받아들였지만, 그런 태도는 역효과만 냈습니다. 나는 다른 모든 리뷰에서처럼 객관적인 마음가짐을 유지했어야 했습니다.
마지막으로 교착 상태를 너무 오래 끌었습니다. 몇 라운드가 지나면 의미 있는 진전이 없다는 것이 분명해졌어야 했습니다. 직접 만나 근본적인 갈등을 해결하거나 매니저에게 에스컬레이션하는 등 과감한 변화를 시도했어야 했습니다.
밥이 잘한 점
밥의 첫 번째 조치인 리뷰 쪼개기는 매우 효과적이었습니다. 3주 동안 고통스럽게 정체되어 있던 리뷰가 갑자기 두 조각의 코드가 머지되면서 진전되었다는 점을 기억하세요. 덕분에 말로리와 밥 모두 앞으로 나아가고 있다는 느낌을 받아 기분이 좋아졌습니다. 남은 덩어리에도 여전히 문제는 있었지만 더 작고 관리하기 쉬운 체인지리스트가 되었습니다.
밥은 리뷰를 완벽하게 만들려고 끝까지 조이지 않았습니다. 그는 아마 내가 그토록 목소리 높여 지적하던 문제들을 똑같이 인식했겠지만, 말로리가 앞으로도 팀에 오래 있을 것이라는 점을 깨달았습니다. 단기적으로 유연하게 대처함으로써 장기적으로 말로리가 품질을 개선하도록 도울 수 있는 입지를 마련한 것입니다.
결론
이 글의 전반부를 발행한 뒤 여러 독자가 내가 권장한 소통 방식에 대해 문제를 제기했습니다. 어떤 이는 그것이 거만하게 느껴진다고 했고, 다른 이는 너무 간접적이어서 오해의 소지가 있다고 우려했습니다.
그 피드백은 합리적이고 예상 가능한 것입니다. 어떤 사람은 간결한 리뷰 코멘트를 무뚝뚝하거나 무례하다고 느낄 수 있습니다. 다른 사람은 같은 코멘트를 간결하고 효율적이라고 평가할 수도 있습니다.
코드를 리뷰할 때 당신은 많은 선택을 합니다. 무엇에 집중할지, 피드백을 어떻게 구성할지, 언제 승인할지 등입니다. 중요한 것은 나의 선택을 따르는 것이 아닙니다. 선택지가 존재한다는 사실을 인식하는 것입니다.
누구도 완벽한 리뷰를 위한 정답을 줄 수 없습니다. 가장 효과적인 기법은 코드 작성자의 성격, 그와의 관계, 팀 문화에 따라 달라집니다. 코드 리뷰의 결과를 비판적으로 생각하며 접근 방식을 다듬으세요. 긴장이 생기면 한발 물러서서 왜 그런 일이 일어났는지 평가해 보세요. 리뷰의 질에 주의를 기울이세요. 코드를 당신의 품질 기준까지 끌어올릴 수 없다고 느낀다면 리뷰 프로세스의 어떤 측면이 방해가 되고 있는지, 그리고 이를 어떻게 해결할 수 있을지 생각해 보세요.
행운을 빌며, 여러분의 코드 리뷰가 인간답기를 바랍니다.
더 읽어보기
- “How to Make your Code Reviewer Fall in Love with You”는 이 글의 자매편으로, 리뷰어가 아니라 작성자 입장에서 코드 리뷰를 개선하는 방법을 설명합니다.
- PuTTY SSH 클라이언트의 저자인 Simon Tatham이 쓴 “Code review antipatterns”는 리뷰어로서 피해야 할 함정들의 유용한 목록을 다룹니다.
이 글은 Samantha Mason이 편집했습니다. 일러스트는 Loraine Yow가 담당했습니다. 이 글의 초기 초안에 귀중한 피드백을 제공해 준 @global4g에게 감사드립니다.
글을 무작위로 읽기











댓글
로그인하고 댓글 남기기