Skip to content
Published on

머지되는 PR과 커밋 메시지 쓰기

Authors

들어가며 — PR은 코드가 아니라 커뮤니케이션이다

PR을 올렸는데 며칠째 리뷰가 안 붙은 경험, 다들 있을 겁니다. 반대로 어떤 PR은 올리자마자 "LGTM"이 찍히고 바로 머지됩니다. 코드 실력의 차이일까요? 꼭 그렇지만은 않습니다. 빨리 머지되는 PR에는 기술과 별개로 커뮤니케이션의 기술이 있습니다.

핵심 관점은 이겁니다. PR을 만드는 순간, 여러분은 코드를 쓴 것이 아니라 리뷰어에게 "이 변경을 이해하고 승인해 달라"는 요청을 보낸 것입니다. 리뷰어는 여러분의 머릿속을 모릅니다. 그가 가진 것은 diff와 설명뿐입니다. 그래서 좋은 PR이란 결국 리뷰어가 최소한의 노력으로 최대한 확신을 갖게 만드는 PR입니다.

이 글은 그런 PR과 커밋 메시지를 쓰는 실전 습관을 정리합니다. Git 명령이 손에 익지 않았다면 이 사이트의 Git 놀이터에서 브랜치와 커밋 흐름을 눈으로 익히고 오는 것도 좋습니다.

작고, 목적이 하나인 PR

가장 효과가 큰 습관 하나를 꼽으라면 단연 PR을 작게 유지하는 것입니다. 1,000줄짜리 PR과 100줄짜리 PR 열 개 중, 리뷰 품질이 높은 쪽은 압도적으로 후자입니다.

이유는 인간의 주의력에 있습니다. 리뷰어가 큰 diff를 마주하면 두 가지 일이 일어납니다. 첫째, 집중력이 떨어져 버그를 놓칩니다. 둘째, 부담스러워서 리뷰를 자꾸 미룹니다. 연구와 현장 경험 모두 리뷰의 결함 발견율이 변경 크기가 커질수록 급격히 떨어진다고 말합니다.

그래서 원칙은 한 PR은 한 가지 목적만 담는 것입니다.

  나쁜 PR: "사용자 프로필 기능"
    - 새 API 엔드포인트 추가
    - 관련 없는 로깅 라이브러리 교체
    - 들여쓰기 스타일 전체 수정
    - 오타 3개 수정
  → 리뷰어: 무엇을 봐야 하지? 이 스타일 변경이 버그를 숨기고 있나?

  좋은 PR 3개로 분리:
    PR 1: 프로필 API 엔드포인트 추가   (기능)
    PR 2: 로깅 라이브러리 교체          (인프라)
    PR 3: 스타일/오타 정리              (잡일)
  → 각각 독립적으로 리뷰·머지·롤백 가능

기능 변경과 리팩터링과 포매팅을 한 PR에 섞으면, 리뷰어는 어느 줄이 "진짜 변경"이고 어느 줄이 "그냥 옮긴 것"인지 구분하느라 지칩니다. 포매팅만 따로 커밋하고, 리팩터링은 별도 PR로 빼세요. 그것만으로 리뷰 속도가 눈에 띄게 빨라집니다.

커밋 메시지: Conventional Commits

커밋 메시지에도 널리 쓰이는 관례가 있습니다. Conventional Commits는 커밋의 첫 줄에 변경의 종류를 접두어로 붙이는 규약입니다.

<타입>(<범위>): <요약>

feat(auth): add password reset via email
fix(api): handle null user in settlement job
docs(readme): clarify local setup steps
refactor(cart): extract price calculation
test(order): add cases for partial refunds
chore(deps): bump lodash to 4.17.21

자주 쓰는 타입은 feat(기능), fix(버그 수정), docs(문서), refactor(동작 변화 없는 개선), test(테스트), chore(빌드·의존성 등 잡일)입니다.

이 규약의 이점은 단순한 통일성 이상입니다. 기계가 커밋을 읽을 수 있게 됩니다. featfix를 구분할 수 있으니 버전을 자동으로 올리고(semantic versioning), 변경 로그(CHANGELOG)를 자동 생성할 수 있습니다. 사람에게도 좋습니다. git log만 훑어도 이 프로젝트에서 무슨 일이 있었는지 한눈에 들어옵니다.

요약 줄(첫 줄)에는 몇 가지 관례가 더 있습니다. 명령형으로 쓰고("added"가 아니라 "add"), 50자 안팎으로 짧게, 마침표는 붙이지 않습니다. "이 커밋을 적용하면 ___ 하게 된다"의 빈칸을 채운다고 생각하면 자연스럽습니다.

"무엇"이 아니라 "왜"를 본문에 담아라

여기가 초급과 중급을 가르는 지점입니다. 초보의 커밋 메시지는 무엇을 바꿨는지를 적습니다. 그런데 그것은 diff가 이미 보여 줍니다. 정말 필요한 것은 왜 바꿨는지입니다.

  약한 메시지 (무엇 - diff에 이미 있음):
    fix: change timeout from 30 to 60

  좋은 메시지 (왜 - diff에 없음):
    fix(upload): raise timeout to 60s for large video uploads

    결제 후 원본 영상(최대 2GB)을 인코딩 서버로 넘길 때
    30초 타임아웃이 자주 초과되어 업로드가 실패했다.
    측정해 보니 p95 전송 시간이 48초여서 여유를 두고 60초로 올린다.
    근본 해결(청크 업로드)은 별도 이슈 #482 에서 다룬다.

아래 메시지는 6개월 뒤 이 코드를 보게 될 사람(대개 미래의 나)에게 결정적입니다. "왜 하필 60초지? 30초면 안 되나?"라는 질문에 미리 답하고 있고, 근본 해결책이 아니라는 점과 후속 이슈까지 연결해 둡니다. 코드는 무엇을 하는지 말하지만, 왜 그렇게 되었는지는 오직 사람만 기록할 수 있습니다. 그 기록의 자리가 커밋 본문입니다.

규칙으로 정리하면, 요약 줄 다음에 빈 줄을 하나 두고, 그 아래 본문에 배경·이유·트레이드오프를 서술합니다. 사소한 커밋(오타 수정 등)은 본문이 없어도 되지만, 결정이 담긴 커밋에는 반드시 "왜"를 남기세요.

올리기 전에 셀프 리뷰부터

PR을 올리기 직전, 남에게 보내기 전에 스스로 diff를 처음부터 끝까지 읽는 습관은 놀랄 만큼 강력합니다. 리뷰어가 되어 내 코드를 읽는 것입니다.

# 올리기 전, 내가 무엇을 바꿨는지 전체를 다시 본다
git diff main...HEAD

셀프 리뷰에서 잡히는 것들은 대개 사소하지만 리뷰어를 짜증나게 하는 것들입니다. 디버깅용 printconsole.log가 남아 있거나, 주석 처리한 죽은 코드가 딸려 왔거나, 실수로 커밋한 임시 파일이 있거나, 커밋 메시지에 오타가 있거나. 이런 것들을 리뷰어가 발견하고 코멘트를 달면, 왕복 한 번이 더 생기고 머지는 그만큼 늦어집니다.

셀프 리뷰는 그 왕복을 미리 없앱니다. "리뷰어가 여기서 무엇을 물어볼까?"를 상상하고, 예상되는 질문에는 코드 옆에 미리 코멘트를 달아 두세요. 예를 들어 "이 부분이 좀 이상해 보일 텐데, 외부 API가 이런 형식으로만 응답해서 어쩔 수 없었다" 같은 자기 코멘트는 리뷰어의 시간을 크게 아낍니다.

좋은 PR 설명: 맥락·변경·테스트

PR 설명은 리뷰어가 diff를 읽기 전에 보는 첫 화면입니다. 여기서 방향을 잘 잡아 주면 리뷰가 훨씬 부드러워집니다. 세 가지 축으로 구성하는 것을 추천합니다.

  ## 맥락 (Context / Why)
  왜 이 변경이 필요한가. 어떤 문제나 요구가 있었는가.
  관련 이슈 링크. 리뷰어가 배경 지식 없이도 이해하도록.

  ## 변경 (What changed)
  무엇을 어떻게 바꿨는가. 큰 diff라면 파일별/영역별로 짧게 안내.
  중요한 설계 결정이 있었다면 그 근거도.

  ## 테스트 (How it was tested)
  어떻게 검증했는가. 추가한 테스트, 수동 확인 절차, 스크린샷 등.
  리뷰어가 "이게 정말 동작하는가"를 믿을 근거.

이 세 축은 리뷰어가 반드시 갖는 세 가지 질문 — "왜 하는 거지?", "무엇을 바꿨지?", "정말 되는 게 맞나?" — 에 순서대로 답합니다. 특히 테스트 섹션을 빼먹지 마세요. UI 변경이라면 전후 스크린샷 한 장이 백 마디 설명보다 강력합니다. 리뷰어는 "이 사람이 검증을 했구나"를 확인하는 순간 안심하고 승인 쪽으로 기웁니다.

리뷰어에 대한 공감

이 모든 습관을 관통하는 하나의 태도가 리뷰어에 대한 공감입니다. PR을 올릴 때, 그 화면 반대편에 시간이 빠듯한 동료가 있다고 상상하세요. 그의 인지 부하를 줄여 주는 모든 행동이 곧 내 PR을 빨리 통과시킵니다.

구체적으로는 이렇습니다.

  • 읽는 순서를 안내하라: "먼저 parser.py를 보고, 그다음 이를 쓰는 main.py를 보면 이해가 쉽습니다" 같은 한 줄이 리뷰어의 길잡이가 됩니다.
  • 큰 결정에는 근거를 미리 달아라: 논쟁이 될 만한 선택("왜 X 대신 Y를 썼나")은 리뷰어가 묻기 전에 설명에 적어 두세요.
  • PR 크기를 리뷰어의 시간에 맞춰라: 급한 핫픽스라면 더더욱 작게. 큰 기능이라도 리뷰 가능한 단위로 쪼개는 배려가 필요합니다.
  • 피드백을 방어적으로 받지 마라: 리뷰 코멘트는 코드에 대한 것이지 인격에 대한 것이 아닙니다. "좋은 지적이에요, 반영했습니다"가 논쟁보다 빠릅니다.

리뷰는 결국 사람 사이의 일입니다. 리뷰어를 돕는 PR을 꾸준히 올리면, 신뢰가 쌓이고 다음 PR은 더 빨리 통과됩니다.

스택 PR로 큰 작업 쪼개기

"작게 유지하라"는 원칙과 "큰 기능을 만들어야 한다"는 현실은 자주 충돌합니다. 이때 유용한 기법이 **스택 PR(stacked PRs)**입니다. 큰 작업을 서로 의존하는 작은 PR들의 사슬로 쪼개, 앞의 PR 위에 다음 PR을 쌓는 방식입니다.

  main
   └── PR 1: DB 스키마 + 마이그레이션      (독립적으로 리뷰)
        └── PR 2: PR 1 위에서 리포지토리 계층
             └── PR 3: PR 2 위에서 API 엔드포인트
                  └── PR 4: PR 3 위에서 UI 연결

각 PR은 바로 아래 PR을 베이스 브랜치로 삼습니다. 덕분에 리뷰어는 200줄짜리 조각 넷을 순서대로 검토하게 되고, 각 조각은 앞의 맥락 위에서 이해됩니다. 800줄을 한 번에 던지는 것보다 훨씬 낫습니다. 앞 PR이 머지되면 뒤 PR의 베이스를 main으로 바꿔 이어 갑니다.

스택 PR은 관리에 약간의 부지런함이 필요합니다. 앞 PR이 수정되면 뒤 PR들을 리베이스해 줘야 하기 때문입니다. 하지만 대부분의 팀 도구가 이를 도와주고, "리뷰 가능한 크기"라는 이득이 그 비용을 충분히 넘어섭니다. 기능이 커질 조짐이 보이면, 처음부터 스택으로 설계하는 것이 나중에 거대한 PR을 쪼개려 애쓰는 것보다 쉽습니다.

마치며

빨리 머지되는 PR의 비결은 화려한 코드가 아니라 리뷰어에 대한 배려입니다. 작고 목적이 하나인 PR, Conventional Commits로 정돈된 히스토리, "왜"를 담은 커밋 본문, 스스로 하는 셀프 리뷰, 맥락·변경·테스트가 담긴 설명, 그리고 큰 작업을 위한 스택 PR. 이 모두가 향하는 곳은 하나입니다. 리뷰어가 최소한의 노력으로 최대한의 확신을 갖게 하는 것.

코드를 쓰는 것은 절반이고, 나머지 절반은 그 코드를 남이 이해하고 신뢰하게 만드는 일입니다. Git 워크플로가 아직 낯설다면 Git 놀이터에서 브랜치와 머지, 리베이스를 직접 움직여 보며 감을 익혀 보세요. 도구가 손에 익으면, 좋은 PR을 만드는 일에 더 많은 여유를 쓸 수 있습니다.

참고 자료