プルリク職人への道

ジョブカン事業部のアドベントカレンダー16日目です!!

https://qiita.com/advent-calendar/2024/jobcan

DONUTSの札幌オフィスでジョブカンの開発インターンに参加している@sjhsです。
開発に携わるうち、「プルリク職人になりたいな」と思った話を書きます。

# 開発スピード、アップしたいですよね
どうやってアップしましょうか。
集中力を高めて開発をしますか？
たくさん勉強をしてつよつよエンジニアになったら、きっと開発スピードも早いですね。
これらの方法は長期的かつ根本的な解決ではありますが、個人の強さに依存する傾向があります。
個人の強さに依存しない方法があるなら、まずはそれを試したいところです。

# もっとも時間がかかる場所
なるべく小さい労力で開発速度を上げるには、もっとも時間のかかる場所にアプローチするのが良さそうです。
issueに着手してからリリースされるまでにもっとも時間がかかる場所はどこでしょうか。
それはプルリクエスト(以下プルリク)を出してレビューが通るまで塩漬けになっている時間です。
なぜ塩漬けになってしまうのでしょうか。その理由は主に以下の3点です。

**1. 変更の意図が伝わっていないから**
コードに変更を加えた部分には必ずそうした意図が存在しています。
レビュワーにプルリクで意図をうまく伝えられなかった場合は、確認のやり取りが発生します。
レビューは今取り掛かっている別のissueの合間に進めるため、確認のやり取りで数時間かかることも珍しくありません。
この数時間ぶんをコードを考えて書く作業の方で短縮しようと試みるのは大変なことです。

**2. プルリクが大きいから**
プルリクが大きくなると変更箇所やテスト項目が増えます。
変更箇所やテスト項目が増えるとレビュワーの負担が増えます。
複数のレビューリクエストが来ていたら無意識のうちに軽いものから見ようと思うのは自然なことです。
チーム全体で出されている未レビュープルリクの数にもよりますが、数日〜数週間の塩漬けとなることも十分あり得ます。

**3. 諸事情による保留**
マージされていないプルリクの中で最も古いプルリクエストを見てみました。
- 実装者が退職済み
- 関連する別のプロダクトの開発チームと足並みを合わせなければマージできない
- 関連プロダクトの開発優先度が変更された

という事情が重なっており、実装者が小手先でどうにかするのは難しいので本記事では主に3.以外について手を打っていきます。

issueに着手してからプルリクを出すまでと、プルリクを出してからリリースされるまででは、後者の短縮を目指す方が圧倒的に労力に対してのコスパが良いです。
小さな労力で大きな結果を獲得しましょう！

# プルリク職人への道
プルリクが塩漬けになってしまう要因のうち、もっとも改善の効果が見込めそうなのは変更の意図が伝わるプルリクを作ることです。
クオリティの高いプルリクを作る「プルリク職人」になりたいものですね。

### 変更の意図を伝えよう
プルリクにおいて、任意で書ける場所は意外と多岐にわたります。
どこに何を書きましょうか。
基本となる方針はt-wadaさんのこちらのツイ...ポストです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">コードには How<br>テストコードには What<br>コミットログには Why<br>コードコメントには Why not<br><br>を書こうという話をした</p>&mdash; Takuto Wada (@t_wada) <a href="https://twitter.com/t_wada/status/904916106153828352?ref_src=twsrc%5Etfw">September 5, 2017</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このポストで特に多くの人が感銘を受けたのはコードコメントのWhy notだと思われます。
「なぜそうしなかったのか」という情報は時とともに失われゆくもので、特に書いた本人が退職した場合は決して手の届かない情報になります。
Why notという情報が書かれていると、
- 一見不自然に見える実装にもそうする理由があったとわかる
- もっと良い方法を考慮した上でその方法を選んでいるとわかるので後続の人がまた同じことを考えるのに労力を費やさなくてよい
- 書き換える必要が生じた場合にもコメントに書かれた事情がその時点で解決してさえいれば書き換えてもいい  

という非常に大きいメリットを享受できます。

WhyとWhy notはまさに、今回伝えたい「変更の意図」そのものです。
コミットログのWhyについてもここで触れたいと思います。

### コミットの粒度
コミットの粒度はどの程度が適切かという議論には諸説あります。
ひとつの目安となるのは、意思決定ひとつにつき一コミット。
言語化可能な意思決定一つにつき一回コミットすることで、レビュワーはプルリクのコミットログを見て実装者の意思の流れを追えるようになります。
とはいえ、今まさに実装している時は「キレイなコミットログなんてわざわざ作ってられるか！」と言う気持ちになるものです。
そこで、キレイなコミットログは後から作る方法をおすすめします。
### キレイなコミットログを作ろう、後付けで
```
$ git rebase -i HEAD~8
```
このコマンドを実行すると、以下のようなvi画面に移行します。
一番直近のコミットから順番に、`HEAD~n`のnに入れた数字分遡ったコミット履歴が出てきます。
```
pick コミットハッシュ コミットメッセージ
```
```
pick 86bbc6518 issue#5000 データ削除ボタンの枠組みを作成
pick 208ee6044 枠組みを作成
pick 2c3ee6174 ボタン作成
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 4225dec75 タイプミス修正
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
今回の例ではGitHubで#5000の架空のissue「データ削除機能を追加する」に取り掛かっているとします。

### コミットメッセージの編集
vi画面なのでこの画面は編集できます。
`pick`のところを別の単語に書き換えることで様々な効果を発揮します。
例えばこのチームではコミットメッセージにissue番号を含めると言うルールがあったため、コミットメッセージを変えたいとします。
適当にコミットしていたため、このルールに沿っていないメッセージがいくつかあるようです。
そんなときは`pick`を`edit`に変えます。
```
pick 86bbc6518 issue#5000 データ削除ボタンの枠組みを作成
edit 208ee6044 枠組みを作成
edit 2c3ee6174 ボタン作成
pick 6985365c5 issue#5000 データ削除確認画面を追加
edit 4225dec75 タイプミス修正
pick 3988db23c issue#5000 キャンセルボタンを追加
edit 75be59ceb 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
各行の右側にもともとのコミットメッセージが表示されていますが、この画面でコミットメッセージを書き換えても特に意味はないです。
頭のpickだけeditに変えたら`:wq`でvi画面を抜けます。
すると、
```
Stopped at 208ee6044...  枠組みを作成
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
```
と表示され、いつも通りコマンドを入力できる状態になります。
今どのような状態かというと、`pick`や`edit`が積み重なったものを処理している途中の段階にいます。
`pick`はそのままスルーされて、`edit`に突き当たると処理が中断されて今のようにコマンドを入力できる画面になります。
ここではコマンドでコミットメッセージを書き換えられます。
```
$ git commit --amend -m "issue#5000 枠組みを作成"
```
新しいコミットメッセージでコミットしたら、処理を先に進めたいので次のコマンドを実行します。
```
$ git rebase --continue
```
すると処理が進み、次の`edit`にしたコミットを読み込んだところでまた止まります。
```
Stopped at 2c3ee6174...  ボタン作成
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
```
同じように`amend`と`continue`で処理を進めていきます。
最後まで進めると`Successfully rebased and updated refs/heads/hogehoge.`と表示され、めでたくコミットメッセージが変わっています。
### コミットの合体
一つの意思決定より細かいレベルでコミットしている場合があると思います。
これらのコミットをまとめて1つのコミットにすることで、後で流れを追いやすくなります。
```
$ git rebase -i HEAD~8
```
```
pick 86bbc6518 issue#5000 データ削除ボタンの枠組みを作成
pick 208ee6044 issue#5000 枠組みを作成
pick 2c3ee6174 issue#5000 ボタン作成
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 4225dec75 issue#5000 タイプミス修正
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
例えば、上3つのコミットは作業の合間にコミットしたもので、意思決定としては1回分です。
そんな時はコミットを合体させていきましょう。
```
pick 86bbc6518 issue#5000 データ削除ボタンの枠組みを作成
squash 208ee6044 issue#5000 枠組みを作成
squash 2c3ee6174 issue#5000 ボタン作成
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 4225dec75 issue#5000 タイプミス修正
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
`squash`を使うとその1つ前のコミットと合体した新しいコミットが作られます。
前のコミットと合体させたいコミットの`pick`を`squash`に変えましょう(合体される側のコミットは`pick`のままにしておきます)。
`:wq`でこの画面を抜けると、またvi画面が出ます。
```
# This is a combination of 3 commits.
# This is the 1st commit message:

issue#5000 データ削除ボタンの枠組みを作成

# This is the commit message #2:

issue#5000 枠組みを作成

# This is the commit message #3:

issue#5000 ボタン作成
```
これは合体したコミットのコミットメッセージを作る画面です。
`# This is a combination of 3 commits.`の下に合体コミットのコミットメッセージを書きます。
```
# This is a combination of 3 commits.

issue#5000 データ削除ボタンを作成

# This is the 1st commit message:

issue#5000 データ削除ボタンの枠組みを作成

# This is the commit message #2:

issue#5000 枠組みを作成

# This is the commit message #3:

issue#5000 ボタン作成
```
`:wq`で抜けると`Successfully rebased and updated refs/heads/hogehoge.`と表示され、めでたくコミットが合体しています。

### コミットの並び替え
例えば、タイプミスがあったのは削除ボタンについてのことだったために、コミットを並び替えたいとします。
```
$ git rebase -i HEAD~6
```
```
pick 86bbc6518 issue#5000 データ削除ボタンを作成
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 4225dec75 issue#5000 タイプミス修正
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
この画面でpickの並び方の順番を入れ替えてあげればコミットの順番も入れ替わります。
```
pick 86bbc6518 issue#5000 データ削除ボタンを作成
pick 4225dec75 issue#5000 タイプミス修正
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
`:wq`で抜けると`Successfully rebased and updated refs/heads/hogehoge.`と表示され、めでたくコミットが入れ替わっています。

### コミットの合体 その2
タイプミスを修正したことが情報として残っている必要性は薄いですね。
このような時は`fixup`を使って前のコミットと合体していきます。
```
$ git rebase -i HEAD~6
```
```
pick 86bbc6518 issue#5000 データ削除ボタンを作成
pick 4225dec75 issue#5000 タイプミス修正
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
タイプミスのコミットの`pick`を`fixup`に書き換えます。
```
pick 86bbc6518 issue#5000 データ削除ボタンを作成
fixup 4225dec75 issue#5000 タイプミス修正
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
pick d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
`:wq`で抜けると`Successfully rebased and updated refs/heads/hogehoge.`と表示され、めでたくコミットが合体しています。
`squash`と`fixup`はどちらも前のコミットと合体する効果があります。
`squash`はコミットを合体させた新しいコミットを作るのに対して、`fixup`は前のコミットに吸収される(コミットメッセージを新しく書かない)という違いがあります。
作業を細かくコミットしたものをまとめるのが`squash`、メインのコミットに微修正コミットを吸収させるのが`fixup`という使い分けがよさそうです。

### コミットの分割
画面を横長にするのと削除ボタンの色を変更したのは別々の意思決定ですが、1つのコミットにまとまっています。
こちらを分割して2つのコミットにしましょう。
```
$ git rebase -i HEAD~5
```
```
pick 86bbc6518 issue#5000 データ削除ボタンを作成
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 3988db23c issue#5000 キャンセルボタンを追加
pick 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
edit d987056bb issue#5000 画面を少し横長にして削除ボタンの色も変更
```
該当コミットを`edit`に変更して抜けるとそのコミットで止まるので、ここで
```
$ git reset HEAD^
```
を実行して一旦この作業をコミットしてない状態にします。
そこから
```
$ git add -p
```
を使うと行ごとに変更をコミットに含めるか聞かれます。
今回の場合は画面を横長にした変更を`y`で含めて削除ボタンの変更を`n`で含めないようにします。
その後、ふつうにコミットします。
```
$ git commit -m "issue#5000 画面を少し横長にした"
```
残りの削除ボタンの変更も別のコミットとします。
```
$ git add -p
$ y
$ git commit -m "issue#5000 削除ボタンの色を変更"
```
これでコミットが分割できたので、処理を先に進めていきます。
```
$ git rebase --continue
```
`Successfully rebased and updated refs/heads/hogehoge.`と表示され、めでたくコミットが分割しています。
コミットの分割は技術的には可能ですが操作を間違えてコミットを消してしまうリスクがある(私も実験中にコミットを間違えて消しました)ので、隙あらば適当なメッセージでコミットしておいて後でコミットをまとめるのがおすすめです。

### 仕上げ
コミットメッセージでWhyを書いていないコミットにWhyを書いていきます。
```
$ git rebase -i HEAD~6
```
```
pick 86bbc6518 issue#5000 データ削除ボタンを作成
pick 6985365c5 issue#5000 データ削除確認画面を追加
pick 3988db23c issue#5000 キャンセルボタンを追加
edit 75be59ceb issue#5000 「キャンセル」を「やっぱやめる」に変更
edit d987056bb issue#5000 画面を少し横長にした
edit a346d2198 issue#5000 削除ボタンの色を変更
```
メッセージを変更したいコミット全部に`edit`をつけて`:wq`で抜けます。
```
Stopped at 75be59ceb...  「キャンセル」を「やっぱやめる」に変更
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
```
`edit`にしたコミットで処理が止まってamendかcontinueかを聞いてくる画面になるので、先ほどと同様amendとcontinueを繰り返して処理を進めていきます。
```
$ git commit --amend -m "世界観に合わせて「キャンセル」を「やっぱやめる」に変更"
$ git rebase --continue
$ git commit --amend -m "ボタン2つを置くには狭いため画面を横長に変更"
$ git rebase --continue
$ git commit --amend -m "削除ボタンが背景色と似ているためボタンの色を変更"
$ git rebase --continue
```
最後まで進めると`Successfully rebased and updated refs/heads/hogehoge.`と表示され、めでたくコミットメッセージが変わっています。

これらのワザを駆使して「自分のためのコミットメッセージ」を「レビュワーのためのメッセージ」「未来の誰かのためのメッセージ」へと変えていきましょう！
## メリットまとめ
- 実装者がコードを書いている間は何も気にせず雑にコミットしてよくなる
- 自分のためのコミットメッセージをプルリクを出すときになってレビュワーのため、ひいては未来のチームのためのメッセージに書き換えられる
- レビュワーがコミットログを見た時、実装者が何を考えていたのか詳しくわかる
- 実装者もプッシュする前に自分の書いたコードを意思決定単位で見返すことによって、その実装にした意図をちゃんと言語化して伝えられるくらい考えられているかどうかを見直せる
- 後で戻したくなったとき、どこに戻せばいいのかはっきりする

このように、コミットを整理するとメリットがたくさん得られます！
コミットを整理するにはある程度手間がかかるものの、これらのメリットはじわじわと開発スピードの増加に貢献することでしょう。

## 留意点
- 自分しか触らないブランチでやりましょう(Gitflowにおけるfeatureブランチでやる分には大丈夫です)
    - rebaseを使うのでコミット履歴が改変されます
- 目指すところはストレートでプルリクを通すことではないです
    - コミットを見返す中で実装の意図を整理すること
    - レビュワーから見て実装者の意図が過不足なく伝わること
        - その上でレビュワーが「その理由であれば〇〇の方が良いのでは？」と感じたら有意義なやり取りになるので積極的に交流したいです
    - 未来の自分やメンバーがログを遡りやすくすること
    - これらの目標が真に目指すところになると考えます

知見が貯まったらプルリク職人・概要欄編も書くかもしれません。
皆様よきプルリクライフを。
## お知らせ
DONUTSでは、新卒中途を問わず積極的に採用活動を行っています。
我々ジョブカン事業部も、一緒に働くエンジニアを募集しています。気になる方はお気軽に応募してみてください。

https://www.donuts.ne.jp/recruit/

https://recruit.jobcan.jp/donuts/show/B001/1396297