こんにちは! LIFULLエンジニアの吉永です。
普段はLIFULL HOME'SのtoC向けCRMチームにてエンジニアリングマネージャをやっています。
本日はチームでGitのコミットメッセージ書式をConventional Commitsに準拠するようにしてから得た知見を紹介したいと思います。
コミットメッセージに書式を導入することでどんなメリットがあるのか?導入前後でどんな変化があったのか?今後の展望についてご興味のある方に参考になれば幸いです。
アジェンダ
Conventional Commitsとは?
人間と機械どちらから見ても読みやすい形式のコミットメッセージ規約です。
公式サイトに分かりやすく概要がまとまっていますので、良かったらこちらも参照してください。
規約と言われると、少し躊躇される方もいるかもしれませんがそこまで複雑な規約ではありません。 よって、導入するにあたってのハードルはさほど高くなく、気楽に始められると思います。
基本形は下記の形になります。
<type>[optional scope]: <description> [optional body] [optional footer(s)]
optionalの部分は必須ではないので、必要最低限の規約を満たす為には<type>
と<description>
を記載すれば良いです。
例えばAという新機能を追加したコミットがあるとします。 その時のコミットメッセージは下記のようになります。
feat: A機能を追加
また、上記機能をリリース後一定期間経過後にA機能のバグを修正した場合は下記のようになります。
fix: A機能で数値に変換できない文字列を入力した場合に例外が発生するバグを修正
こんな感じで、人間から見てもそのコミットでどんな変更がコードに加えられたのかが分かりやすい規約になっています。
公式サイト上で定義されている<type>
はfixとfeatのみなのですが、私達のチームではこれらに加えてAngularの規約も取り入れて運用しています。
チームで導入するにあたってどんな工夫をしたか?
自身が率先して規約に準拠する
まずは導入しようというチームに提案した私自身がきちんと規約を理解し、自身が行うコミットコメントを規約に準拠したものにしました。
定期的に自身のコミットメッセージを振り返る時間を設けた
GitHubの検索APIを利用して、過去1週間以内のチームメンバーそれぞれのコミットコメント一覧を週一で開催しているチームの定例時間内で規約に準拠したコメントになっていたかを確認するようにしました。
最低でも1週間に一度は振り返る機会を設けたことで、徐々に規約に従うことが当たり前になっていく効果があったと今は思います。
実際に運用してみて得た知見を仲間にも共有する時間を設けた
得た知見もチームの定例で共有する時間を設けました。
このコミット<type>
はrefactorかfixかchoreか分類に少し迷った、<scope>
をどこで切るか少し迷った、など実際に利用し始めて気づく疑問も数多くあったので、様々な事例を共有することで、次回からの指針になったり徐々に効率的になっていくと思っています。
導入してみてどうか?
リリースノートの内容を分かりやすく自動生成できるようになった
私達のチームではリリース時にセマンティックバージョニング形式に準拠したタグをインクリメントして自動生成していました。
約2年前に自前で作成したGitHub Actionsで行っていましたが、メジャー、マイナー、パッチをインクリメントする際のアルゴリズムがいまいちで、全ての場面においてチームメンバー全員が納得できるバージョニングになっていたかと言われると少し怪しい出来でした。
そんな中、チームのメンバーが下記のGitHub Actionsを探してきてくれ、Conventional Commitsに準拠していれば、タグのインクリメントとリリースノートも自動で生成してくれるという、既存のものよりも優れたものでした。
導入に当たってのハードルも低かったので早速採用して、運用を開始しました。
こちらのGitHub Actionsは個人的にも非常に気に入っており、Conventional Commitsに準拠することで得られる具体的なメリットとして、チーム内外にも共有しました。
コミットの粒度が個人でばらつきにくくなった
Conventional Commitsを採用し、オプションである<scope>
もなるべく含めようというローカルルールを採用したところ、コミットの粒度が個人でばらつきにくくなりました。
私達のチームのプロダクトではクリーンアーキテクチャを採用しているのですが、<scope>
にクリーンアーキテクチャの各レイヤー名(feat(repository):hoge
とかfeat(domain):fuga
みたいな感じです)を入れて運用してみたところ、おのずと一つのコミットでの変更箇所が狭まったからだと思います。
また、レビューでもらった指摘を修正した後も、今まではついつい下記のようなコミットメッセージにしてしまっていました。
レビュー指摘修正
このコミット履歴は後でコードを読み返す時にはノイズにしかならないので、本来この変更を一緒に含めるべきコミットと統合すべきという意識にConventional Commits採用後は変わりました。
具体的には下記のようなコミット履歴になった場合は、
1 feat: A機能を追加 2 test: A機能のテストコードを追加 3 fix: A機能実装部分に対するレビュー指摘修正
下記のように1のコミットに3のコミットを統合するべきですね。
1 feat: A機能を追加 2 test: A機能のテストコードを追加
このようにコミット履歴をマージ前であれば、本来あるべき履歴へマージ前にリベースするということが自然と浸透しました。
レビューをしやすくなった
コミットの粒度がばらつきにくくなったことで、レビューもコミット単位で行いやすくなり、この変更は何で行ったのか?をレビューアが理解しやすくなったと思います。
結果として、レビューがしやすくなったと感じる人が多く、今後も継続していこうと思える一つの要因になっています。
feat系の同じ文脈のコミットはレビュー完了後はまとめた方がよいかも
レビューしやすくなったと感じる件と少し関りがあるのですが、私達のチームでは先述したようにクリーンアーキテクチャのレイヤーに沿った<scope>
でコミットを分けて運用してました。
先日、Conventional Commitsを採用してから初めて大規模な実装を行い、上記の運用でコミット粒度を分けていたのでレビューは非常にやりやすかったです。
ただし、リリースノートにのるfeat一覧としてみると、正直このリリースで何の機能をリリースしたのか?が細分化されすぎているため、初見では分かりづらかったです。
コミット履歴としては下記のような感じになっていました。
feat(domain): A機能用のエンティティと変換処理を実装 test(domain): A機能用のエンティティの変換処理のテストを実装 feat(repository): A機能用のhogehogeデータを外部から取得する処理を実装 test(repository): A機能用のhogehogeデータを外部から取得する処理のテストを実装 refactor(repository): A機能用に追加した処理と既存処理とで共通化できる個所をまとめる feat(usecase): A機能用のインタラクターを実装 test(usecase): A機能用のインタラクターのテストを実装 feat(controller): A機能用のコントローラーを実装 test(controller): A機能用のコントローラーのテストを実装
コミット上ではコントローラー、リポジトリ、ユースケース、ドメインそれぞれ分けた方がレビューはしやすかったのですが、リリース後しばらくたってからコードリーディングをするときのコミット履歴としては下記が良いのではないか?とチームのメンバーと話していて、意見が出ました。
feat(api): A機能を追加 refactor(repository): A機能用に追加した処理と既存処理とで共通化できる個所をまとめる
結論、feat系はレビュー完了後は同じ機能に関するものであれば一つにまとめ、それら以外のものはそのまま個別のコミットのままで良いかもとなりました。
時間が経過して、後からコードリーディングする際のコミットコメントしてもfeat(api): A機能を追加
の方が、「このソースはA機能追加時に改修されたものなのか」と理解しやすく、その際にクリーンアーキテクチャのレイヤーの情報はいらないだろうからという理由です。
※この辺りは、私達の出した結論が必ずしも正解とは限らないと思いますので、ケースバイケースかなと思いますが、一つの知見として参考になれば幸いです。
今後の展望
コードリーディングがしやすくなる(と思っている)
Conventional Commitsに準拠することで、コミット粒度や形式がある程度まとまっていることから、時間が経過した後で該当個所のコードは何の作業でどんな理由が合って改修されたのか?をコミット履歴から情報を得やすくなり、結果としてコードリーディングがしやすくなると思われます。
また、自身だけでなく第三者にとっても同様の効果はあると思うので、効果を実感できる時期が来ることを今から楽しみにしています。
コミットtypeやscopeの内訳を集計できるようになる
既に少しだけ運用を開始したのですが、ある期間にチームで行った開発の割合は新機能開発系とリファクタリング系どんなバランスだったか?そのバランスは適切か?などをConventional Commitsに準拠していくことで定量的に集計することができるようになります。
今まではプロジェクト管理ツールへ日々の工数入力で行っていた集計をGitのコミット履歴の観点からも分析できるようになり、チームとしてより正確なステータスを把握しやすくなることを期待しています。
下のグラフは私達のチームの2023/06~2023/07のConventional Commitsの<type>
を集計した結果です。
現時点では、この割合が健全な状態なのか?はまだあまり良く分かっていませんが、いずれデータが蓄積されて期間比較できるようになっていくことで、より活用の幅が広がっていくと思います。
まとめ
Conventional Commitsについて、採用後しばらく運用してみてチームで得た知見について紹介しました。
チームで導入するには少しハードルが高いなど、それぞれ事情はバラバラかと思いますが、そのような状況の場合に私からお勧めするのは、まずは自分ひとりだけでもいいから準拠してみることだと思います。
私自身、実際に自分で採用してみて得たことをメンバーにも共有したり、Qiitaに個人として得た気づきをアウトプットしたりして理解を深めていったことで、良さに気づけた気がします。
まずはスモールスタートでやってみることで得られることもあると思いますので、是非お試しください!
最後に、LIFULLでは共に成長できるような仲間を募っています。
よろしければこちらのページもご覧ください。