はじめに
入社して数ヶ月、自分の git log はこうでした。
* 9f3a1b2 fix
* 7c2d4e5 fix
* 3a8b9c0 修正
* 1e2f3a4 update
* 5d6e7f8 fix
* 2b3c4d5 ECSスタック追加
先輩に一言言われました。
「これ、3ヶ月後の自分が読んで意味わかると思う?」
正直、そのときは「コードを見ればわかるじゃないですか」と思っていました。
でも今なら先輩が言いたかったことがよくわかります。今回はその「コミットメッセージそのもの」と「コミットの粒度」の話をします。
この記事は「Git実務シリーズ」の第3弾です。
シリーズを通して「Gitを履歴管理ツールとして使う」をテーマに、実務で学んだことを整理しています。
- 第1弾:Git初心者の頃わからなかった「pullするな」の意味
- 第2弾:PRを出す前にコミット履歴を整えろと先輩に叩き込まれた話 〜git rebase -i 入門〜
- 第3弾:本記事 ← 今ここ
なぜコミットメッセージが重要なのか
git log は「変更の年表」
git log はただの履歴一覧ではありません。「このプロジェクトで何が起きてきたか」を時系列で追えるドキュメントです。
メッセージが fix ばかりだと、年表の中身が全部「何かあった」になります。
障害調査のときに命綱になる
本番で障害が起きたとき、「いつ、何の変更が原因か」を特定するために git log や git blame を使います。
git blame はファイルの各行が「どのコミットで変更されたか」を表示するコマンドです。
ただし git blame の出力には作者・日時・短縮ハッシュ・行の内容までしか出ず、コミットメッセージは表示されません。
なので「git blame で犯人コミットのIDを特定 → git show <commit-id> でメッセージと差分を確認」という2段階の流れで使うのが定番です。
問題の行を git blame したら fix というコミットに当たった、という状況を想像してください。
$ git blame ecs.ts
...
7c2d4e5 (yourname 2024-03-15) desiredCount: 1,
...
$ git show 7c2d4e5
commit 7c2d4e5xxxxxxxxxxxxxxxxxxxxx
Author: yourname <yourname@xxxxxx>
Date: Wed Mar 15 09:09:58 2024 +0900
fix
「fix って何を fix したんだ?」となります。コミットの中身を全部読むしかありません。
$ git blame ecs.ts
...
a3f9c12 (yourname 2024-03-15) desiredCount: 1,
...
$ git show a3f9c12
commit a3f9c12xxxxxxxxxxxxxxxxxxxxx
Author: yourname <yourname@xxxxxx>
Date: Wed Mar 15 09:09:58 2024 +0900
ECS desiredCount を2→1に変更(コスト削減、本番は別パラメータで上書き)
一方、こうなっていたら、「あ、これは意図的に変えた行だ」とすぐわかります。
PR レビューの文脈にもなる
レビュアーはコードだけを読んでいるわけではありません。コミット履歴も読んでいます。
「このコミットで何をしたのか」がわかれば、変更の意図を理解しながらコードを読めます。
メッセージが fix だと、レビュアーは「なぜこう書いたのか」を推測しながら読むしかなくなります。
先輩がメッセージにこだわっていた理由
当時は「コードが動いてるんだからメッセージなんてどうでもいい」と思っていました。
でも実際に自分がレビュアーになってみると、コミットメッセージが整っているかどうかで、レビューのしやすさが全然違うことがわかりました。
そして、障害対応で深夜に git blame を叩いたとき、コミットメッセージに「なぜこの変更をしたか」が書いてあると、本当に助かります。
逆に fix しか書いていないコミットに当たったとき、「誰だこれ書いたの」と思ったら自分でした、という経験を何度かしました。
先輩が言いたかったのは「ルールを守れ」ではなく、「未来の誰かへの配慮としてメッセージを書け」ということだったんだと思います。
悪いメッセージ / 良いメッセージ
具体例で見てみます。
❌ 悪いメッセージ
fix
update
修正
いろいろ
対応
WIP(push したまま)
ちょっと直した
CDK修正
共通しているのは「何をしたか」すら伝わらないことです。
⭕ 良いメッセージ
ECS タスク定義のメモリ上限を512→1024MBに変更(OOMKill 対策)
NATゲートウェイをシングルAZに変更(開発環境のコスト削減)
RDSのバックアップ保持期間を7日→30日に変更(要件変更 #456)
S3バケットのパブリックアクセスブロックが無効になっていたため有効化
検証環境で不要になったCloudWatch Alarmを削除
「何をしたか」だけでなく「なぜしたか」「どう変わったか」が伝わります。
what ではなく why を書く
これがコミットメッセージで一番大事な考え方です。
コードを読めば「何をしたか(what)」はわかります。でも「なぜしたか(why)」はコードからは読み取れません。
❌ セキュリティグループ修正
⭕ 踏み台サーバのSGで全開放になっていた22番ポートを社内CIDRのみに制限
❌ パラメータ変更
⭕ ECS desiredCount を2→1に変更(開発環境のコスト削減、本番は parameter.ts で上書き)
❌ IAMポリシー追加
⭕ LambdaにS3:GetObjectを追加(KBのデータソース取得で権限エラーが発生したため)
「なぜ」を書くと、数ヶ月後に git blame でそのコミットに当たったとき、変更の背景がわかります。
コミットの粒度:1コミット1意図
メッセージと同じくらい重要なのが「粒度」です。
大きすぎるコミット
* a1b2c3d VPC・ECS・RDS・ALB・IAM・CloudWatch全部追加
1コミットに複数の変更が混在しています。
- レビュアーが diff を追いにくい
- 「RDS の設定だけ revert したい」ができない
-
git blameで当たったとき、関係ない変更も混じっている
細かすぎるコミット
* 9f3a1b2 fix typo
* 7c2d4e5 fix typo2
* 3a8b9c0 fix typo3
* 1e2f3a4 コメント追加
* 5d6e7f8 コメント修正
これは前回の記事で扱った git rebase -i でまとめるべき状態です。
ちょうどいい粒度
「1コミット1意図」が基本です。
* a1b2c3d VPCスタックを追加(パブリック・プライベートサブネット、NATゲートウェイ)
* b2c3d4e ECSスタックを追加(Fargate、ALB、タスク定義)
* c3d4e5f RDSスタックを追加(Aurora Serverless v2、サブネットグループ)
それぞれが独立した意図を持っています。「RDS の設定だけ revert したい」という状況でも、c3d4e5f を revert すれば済みます。
「構成追加」と「リファクタリング」は分けておくと特に便利です。
* d4e5f6a ECSのセキュリティグループ定義をNetworkConstructに移動(リファクタリング)
* e5f6a7b ECSタスクにCloudWatch Logsへの書き込み権限を追加
リファクタリングだけを revert したい、権限追加だけを cherry-pick したい、という操作がしやすくなります。
「1意図かどうか」の判断基準
「1コミット1意図」と言われても、最初は「これは1意図?2意図?」の判断がつきません。実用的な目安として、自分はこう判断しています。
コミットメッセージを書こうとしたときに「〜と〜」「〜したついでに〜」と接続詞でつなぎたくなったら、たぶん2コミットに分けるべきサインです。
例えば「ECSタスク定義のメモリを増やしたついでに desiredCount も変更」と書きたくなったら、このコミットは2つに分けたほうがいいです。
「変更したファイルをまとめてコミットしてしまったけど、後から分割したい」という場合は、git add -p でファイルの一部分だけをステージングできます。
プレフィックスで変更を分類する文化
チームによっては、コミットメッセージに「変更の種類」を示すプレフィックスを付ける文化があります。形式はチームによって異なりますが、考え方は共通しています。
Conventional Commits 形式
代表的なのが Conventional Commits という規約です。自分が初めて参画したチームがこの形式を採用していて、最初は「これは feat なのか fix なのか…」で結構迷いました。
feat: ECSスタックにオートスケーリングポリシーを追加
fix: NATゲートウェイのEIPがリリースされていなかったのを修正
docs: README にcdk deployの前提条件を追記
refactor: セキュリティグループ定義をNetworkConstructに集約
test: VPCスタックのスナップショットテストを追加
chore: aws-cdk-libを2.178.2にアップデート
主なプレフィックスはこちらです。
| プレフィックス | 意味 |
|---|---|
| feat | 新機能追加 |
| fix | バグ修正 |
| docs | ドキュメントのみの変更 |
| refactor | 機能変更を伴わないコード整理 |
| test | テストの追加・修正 |
| chore | ビルド設定・依存関係など |
| perf | パフォーマンス改善 |
メリットは以下のとおりです。
-
git log --oneline --grep="^feat"で機能追加だけ絞り込める - CHANGELOG の自動生成ツールと連携できる
- レビュアーが「これはバグ修正か、機能追加か」を一目で判断できる
迷ったときの自分なりの基準は、「動作が変わるなら feat か fix、変わらないなら refactor か chore」です。
ブラケット形式
[FIX] [ADD] [WIP] のような大文字ブラケット形式を使うチームもあります。
[ADD] ECSスタックにオートスケーリングポリシーを追加
[FIX] NATゲートウェイのEIPがリリースされていなかったのを修正
[WIP] は「作業中(Work In Progress)」を示すプレフィックスで、「このコミットはまだ完成していない」という意思表示です。
ただし、コミットメッセージに [WIP] を付けるよりも、GitHub の Draft Pull Request 機能を使う方が今は主流です。Draft PR にしておけば「まだレビューしないで」が PR 単位で明示できますし、マージできない状態にもなるので事故防止になります。
どちらの形式でも「変更の種類を分類する」という考え方は同じです。
チームが使っていないのに自分だけ導入しても浮くだけなので、まずはチームのルールを確認して合わせることが先決です。
コミットメッセージの構造
長めのメッセージを書くときは、構造を意識すると読みやすくなります。
1行目:50文字以内のサマリ(何をしたか)
(空行)
3行目以降:本文(なぜしたか、どう変わったか、注意点など)
実例:
ECS desiredCount を2→1に変更(開発環境のコスト削減)
開発環境では常時2タスク起動している必要がないため1に変更。
本番環境は parameter.ts の prodEnv で上書きしているため影響なし。
コスト削減効果はタスクサイズによるが、概算で月数十ドルの見込み。
関連: issue #234
実際にやってみてつまずいたポイント
push 済みのメッセージを直したい
「push する前に気づけばよかったのに、push 後にメッセージの誤りに気づいた」という状況はよくあります。
まだ誰も触っていない自分だけの feature branch なら、git commit --amend や git rebase -i の reword で直して --force-with-lease で push し直せます。
これは第2弾で扱った操作です(--force-with-lease は通常の --force より安全な force push の方法で、自分の知らない他人のコミットを誤って上書きするのを防いでくれます)。
一方、main や共有ブランチに merge 済みのコミットは直さないのが原則です。
履歴の書き換えは他のメンバーに影響するので、「merge 前に整える」習慣をつけることが根本的な解決になります。
コミット前にメッセージを考えるのが面倒で結局 fix にしてしまう
「ちゃんと書こうとは思っているけど、コミットするときに面倒になって fix と打ってしまう」というのは正直あるあるです。
対策として、Git のコミットメッセージテンプレートを設定しておくと少し楽になります。
git config --global commit.template ~/.gitmessage
# ~/.gitmessage
# 1行目: 何をしたか(25〜30文字目安)
# なぜしたか・背景・注意点
エディタを開いたときにテンプレートが表示されるので、「何か書かないと」という気持ちになります。
1行目が長くなりすぎる
1行目が長くなるときは、コミットが大きすぎるサインでもあります。「〜と〜と〜を修正」という形になっていたら、分割を検討してみてください。
まとめ
コミットメッセージは「未来の自分への手紙」です。
-
fixupdate修正は書かない。何をしたか、なぜしたかを書く - what ではなく why を意識する
- 1コミット1意図を基本にする(接続詞でつなぎたくなったら分割サイン)
- Conventional Commits などのプレフィックス文化はチームに合わせて使う
- 1行目は短く、詳細は本文に
Git は単なるバックアップツールではなく、履歴管理ツールです。
第1弾では「リモートから取り込むときの rebase」、第2弾では「push 前のコミット整理」、そして今回は「メッセージと粒度」を扱いました。
共通しているのは「履歴は未来の自分やチームメンバーが読むものだ」という考え方です。
読みやすい履歴を作ることが、良いエンジニアリングの一部だと思っています。
Git実務シリーズ
- 第1弾:Git初心者の頃わからなかった「pullするな」の意味
- 第2弾:PRを出す前にコミット履歴を整えろと先輩に叩き込まれた話 〜git rebase -i 入門〜
- 第3弾:本記事(コミットメッセージと粒度)