この記事は、ラクス Advent Calendar 2024の8日目の記事です。
初のAdvent Calendarでの記事、頑張ります!
はじめに
エンジニア3年目のdaina-teranishiです。
最近読んだ脳に収まるコードの書き方―複雑さを避け持続可能にするための経験則とテクニックにて、Gitのコミットについて学びました。
「これさえ守れば絶対に大丈夫!」というわけではありませんが、良いコミットとは何か?を考えるきっかけになったので、学んだ内容を踏まえて、コミットに対する現在の考えをまとめます。
何のためにコミットするのか
git commit
とはリポジトリへコードの変更を記録するコマンドです。
変更を記録する際は、コミットメッセージと一緒に記録することができます。
これだけだと変更を記録するためにコミットすると思いがちですが、そうではありません。
変更をチームメンバーと将来の自分へ共有するためにコミットするのです。
変更した本人は前提知識を持っているため、コードの変更部分を見れば、なぜその変更を行なったかを理解することができます。
しかし前提知識を持たないチームメンバーにとっては、なぜその変更を行なったかを理解することは難しいです。
将来の自分が作業する際にも、なぜこの変更を行なったのかを忘れている場合が多いです。
※多くの場合、設計書の確認や作業者への口頭確認(既に作業者がチームにいなければ推測)といったことが必要になってしまいます。
そうなると、当時の設計思想を理解できないまま変更を重ねることになり、技術的負債や最悪の場合バグを埋め込んでしまうことに繋がります。
コミットは単に変更を記録するためではなく、なぜ変更を行なったかを共有するためにコミットしましょう。
そこで大事なのがコミットメッセージです。
コミットメッセージをうまく活用し、なぜその変更を行なったか、どういう設計思想でこのような実装なのかを共有するようにしましょう。
あなたが書いて保存するものは、すべて将来のあなたとチームメンバーに対するメッセージです。
~~ 中略 ~~
書くことよりコミュニケーションに注力せよ
※「脳に収まるコードの書き方 9.1.1 コミットメッセージ」より抜粋
どのようにコミットメッセージを書くべきか
コミットメッセージにはなぜその変更を行なったかを書くようにしましょう。
陥りがちなケースとしてはfeat: パラメータを追加
やrefactor: 予約処理を別クラスに移動
といったコミットメッセージだけのコミットです。
変更内容はコードの差分を見ればわかります。
変更内容をコミットメッセージに含めること自体を否定しているわけではありません。
コミットメッセージの1行目には変更内容のサマリーを書くことが良いとされているため、1行目がfeat: パラメータを追加
になることはあると思います。
ただし3行目以降(2行以上書く場合は、1行目の後は改行)にはその理由を書くようにしましょう。
feat: パラメータを追加
* 予約時に支払い方法を指定できるようにするため追加する
* このパラメータに対するバリデーションは自動生成したため、テストコードは追加しない
* 他のバリデーションを追加する場合はテストコードを追加すること
どのような単位でコミットするべきか
変更の理由が書かれていれば自由にでコミットして良いわけではありません。
1コミットが極端に大きすぎる状態(1機能分)やアプリケーションが動作しない状態でのコミットは避けましょう。
Gitは任意のコミット時点にすぐに戻すことができます。これにより、ある時点へ簡単に切り戻すことができたり、逆に実験的にコードを書いてから考えるといったことが可能になります。
しかし1コミットが極端に大きくなってしまっていると、実装のついでにリファクタリングした箇所でバグを仕込んでしまっていたら、その実装丸々を切り戻すことになります。
また、コミットごとにアプリケーションが動作していなければ、切り戻した先でも正しく動作せず、さらに切り戻すことになってしまいます。
なので、アプリケーションが動作する小さな単位でコミットするようにしましょう。
コミット履歴は、動作するソフトウェアのスナップショットの連続であるべきです。
動作しないコードをコミットしてはいけません。
一方で、コードを正常にビルドできたら、そのたびにコミットしてください。
マイクロコミットしましょう。
※「脳に収まるコードの書き方 9.1.3 小さなコミット」より抜粋
ただし、小さすぎるコミットもあまり良くないと考えています。
例えば予約数の上限チェックを追加する場合、chore: 予約上限数を設定ファイルに追加
-> feat: 予約数を超えていたらエラーにする
-> test: 予約数のチェック処理のテストを追加
といったコミットは小さすぎると考えます。
chore: 予約上限数を設定ファイルに追加
だけを見ても、なぜそこに定義する必要があったのかが理解しにくく、feat: 予約数を超えていたらエラーにする
においても一定の品質が担保された状態でコミットするべきです。
なのでこれらのコミットは、feat: 予約数を超えていたらエラーにする
にまとめて、なぜ設定ファイルに追加したのかをコミットメッセージに書きましょう。
一貫したコミット
アプリケーションが動作する小さな単位でコミットしていれば、安全に切り戻すことができるし、なぜその変更を行なったか理解しやすくなります。
ただし、悩んだ履歴をコミット履歴に残す必要はないと考えます。
例えばfeat: 予約数を超えていたらエラーにする
-> refactor: 予約数のチェックはdomain層で行う
-> refactor: やっぱりpresentaion層で行う
みたいな形です。
たとえ各コミットで動作が保証されていたとしても、他のチームメンバーやレビューワーにとってはノイズになり得ります。
実装者が考えるベストな実装でコミットし、指摘を受けたら修正する方が良いと考えます(usecase層ではなくpresentation層で上限チェックをするならその理由をコミットメッセージに書く)。
Gitにはコミットを編集するコマンドが用意されているため、これらを活用し一貫したコミットを保ちましょう。
コミットの編集でよく使うコマンド↓
-
git commit --amend
- 直前のコミットを編集する
-
git rebase -i HEAD~{n}
- n個前のコミットから修正する
【番外編】良いPR
コミットの話から少し広がってしまいますが、PRはレビューしてもらいたいコミットの集合と考えています。
変更した内容は最終的にデフォルトブランチ(多くの場合、mainブランチ)へ反映してもらう必要があります。
そのため、PRにも「どのような変更を行なったか」を書く必要があり、大きすぎず一定の品質を担保した状態が求められます。
ですが、PRに含まれるコミットが良いものであれば、これらは難しくありません。コミットのサマリーを書き、自分自身がベストと思う状態でPRを出せば、自然と良いPR(≒レビューしやすいPR)になります。
「良いコミットを作るのが難しい」という意見もあると思います。
それであれば先にPRの内容を箇条書きで書き出してみると良いでしょう。そうすればその1つ1つがコミット単位になります。(これは先輩エンジニアから教えてもらい、めっちゃ良い!と思った方法です。)
必要であれば書き出した内容をレビューワーに見せて相談するのもありだと思います。
そうすれば「この変更はやりすぎじゃない?」や「この部分は別PRで出してもらうとレビューしやすい」といった意見がもらえるはずです。
箇条書きで書き出す程度ならコードを見てもらうよりも手軽で、手戻りも少ないため、良い方法だと考えます。
まとめ
今回は脳に収まるコードの書き方―複雑さを避け持続可能にするための経験則とテクニックで学んだ内容を踏まえて、Gitのコミットに対する考えをまとめました。
実際、コミットが多少汚いという理由でPRが拒否されることは少ないですし、マージされた後にコミットメッセージを修正するなんてことはないです。
良いコミットでなくても機能開発を進めることはできます。
(良いコミットだからといって、顧客に提供できる価値への貢献度はかなり間接的なようにも思えるし、、、)
とはいえ、一緒に働くチームメンバーや将来の自分のことを考えると、良いコミットにしようとする努力はやはり必要です。
今後コミットに対する考え方も変わっていくかと思いますが、良いコミットとは何かを考えながらエンジニアとして成長していきたいと考えます。