- はじめに — PRはコードではなくコミュニケーションだ
- 小さく、目的が一つのPR
- コミットメッセージ:Conventional Commits
- 「何を」ではなく「なぜ」を本文に込める
- 上げる前にまずセルフレビュー
- 良いPR説明:コンテキスト・変更・テスト
- レビュアーへの共感
- スタックPRで大きな作業を分割する
- おわりに
- 参考資料
はじめに — PRはコードではなくコミュニケーションだ
PRを上げたのに何日もレビューが付かない、という経験は誰にでもあるでしょう。逆に、上げた瞬間に「LGTM」が付いてすぐマージされるPRもあります。コード力の差でしょうか? 必ずしもそうではありません。早くマージされるPRには、技術とは別に コミュニケーションの技術 があります。
核心となる捉え方はこうです。PRを開いた瞬間、あなたはコードを書いたのではなく、レビュアーに「この変更を理解して承認してほしい」という依頼を送ったのです。レビュアーはあなたの頭の中を知りません。彼が持っているのは diff と説明だけです。だから良いPRとは、結局 レビュアーが最小の労力で最大の確信を得られるPR です。
この記事では、そうしたPRとコミットメッセージを書くための実践的な習慣を整理します。Gitコマンドがまだ手に馴染んでいないなら、このサイトの Gitプレイグラウンド でブランチとコミットの流れを目で掴んでおくのも良いでしょう。
小さく、目的が一つのPR
最も効果の大きい習慣を一つ挙げるなら、間違いなく PRを小さく保つこと です。1,000行のPRと100行のPRが10個とでは、レビュー品質が高いのは圧倒的に後者です。
理由は人間の注意力にあります。レビュアーが大きな diff に直面すると、二つのことが起きます。第一に、集中力が落ちてバグを見逃します。第二に、負担が大きくてレビューをつい先延ばしにします。研究でも現場の経験でも、レビューの欠陥発見率は変更が大きくなるほど急激に下がると言われます。
だから原則は 一つのPRには一つの目的だけ を込めることです。
悪いPR:「ユーザープロフィール機能」
- 新しいAPIエンドポイントを追加
- 無関係なロギングライブラリを差し替え
- インデントのスタイルを全体的に修正
- タイポを3つ修正
→ レビュアー:何を見ればいい? このスタイル変更がバグを隠していないか?
3つの良いPRに分割:
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(ビルドや依存関係などの雑務)です。
この規約の利点は単なる統一性以上のものです。機械がコミットを読めるようになります。feat と fix を区別できるので、バージョンを自動で上げ(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秒に上げる。根本解決(チャンクアップロード)は別途
issue #482 で扱う。
下のメッセージは、6か月後にこのコードを見る人(たいていは未来の自分)にとって決定的です。「なぜちょうど60秒? 30秒ではだめ?」という問いに前もって答え、根本解決ではない点と後続のissueまでつないでいます。コードは何をするかを語りますが、なぜそうなったかは人間だけが記録できます。その記録の場所がコミット本文です。
ルールとして整理すると、要約行の次に空行を一つ置き、その下の本文に背景・理由・トレードオフを述べます。些細なコミット(タイポ修正など)は本文がなくても構いませんが、決定を含むコミットには必ず「なぜ」を残しましょう。
上げる前にまずセルフレビュー
PRを上げる直前、他人に送る前に 自分で diff を最初から最後まで読む習慣 は驚くほど強力です。レビュアーになったつもりで自分のコードを読むのです。
# 上げる前に、自分が何を変えたか全体を見直す
git diff main...HEAD
セルフレビューで捕まるのは、たいてい些細だがレビュアーをいらだたせるものです。デバッグ用の print や console.log が残っていたり、コメントアウトした死んだコードが紛れ込んでいたり、誤ってコミットした一時ファイルがあったり、コミットメッセージにタイポがあったり。こうしたものをレビュアーが見つけてコメントすると、往復が一度増え、マージがその分遅れます。
セルフレビューはその往復を前もって消します。「レビュアーはここで何を尋ねるだろう?」を想像し、予想される質問にはコードの隣に先回りしてコメントを添えましょう。たとえば「この部分は少し変に見えるかもしれないが、外部APIがこの形式でしか応答しないので仕方なかった」という自己コメントは、レビュアーの時間を大きく節約します。
良いPR説明:コンテキスト・変更・テスト
PR説明は、レビュアーが diff を読む前に見る最初の画面です。ここで方向をうまく示せば、レビューはずっとスムーズになります。三つの軸で構成することをお勧めします。
## コンテキスト (Context / Why)
なぜこの変更が必要か。どんな問題や要求があったか。
関連issueへのリンク。レビュアーが前提知識なしで理解できるように。
## 変更 (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を作ることにもっと余裕を使えるようになります。
参考資料
- Conventional Commits 規約: https://www.conventionalcommits.org/
- Google Engineering Practices, "The CL author's guide": https://google.github.io/eng-practices/review/developer/
- "How to Write a Git Commit Message" (Chris Beams): https://cbea.ms/git-commit/
- GitHub Docs, "About pull requests": https://docs.github.com/en/pull-requests
현재 단락 (1/74)
PRを上げたのに何日もレビューが付かない、という経験は誰にでもあるでしょう。逆に、上げた瞬間に「LGTM」が付いてすぐマージされるPRもあります。コード力の差でしょうか? 必ずしもそうではありません。...