はじめに
先日参加したRails Developers Meetupの中で、「コミットの粒度がわからない問題」が少し話題になっていました。
「commitの粒度がわからない」すいません、私もです…!よく迷っちゃいます…!!! #railsdm
— まえとー (@maetoo11) July 20, 2017
commitの粒度がわからない問題、ある。(ほんとわからない) #railsdm
— おおた (@ota42y) July 20, 2017
普段僕は感覚的に「それっ、ここでコミット!」とコミットしているんですが、具体的にどういうルールでやってるの?と聞かれると、きれいに明文化しづらいものです。
とはいえ、できるだけ明文化できるよう、模範解答を考えてみました。
この記事ではそんな「適切なコミット粒度」について解説します。
動画で明文化・・・!?
すいません、「明文化した模範解答」というのは半分ウソです
なかなかパシッ!とルールを明文化できないので、いったん動画の中でつらつらと「コミットの粒度」について語ってみることにしました。
それがこちらの動画です。
【初心者向け】「コミットの粒度がわからない問題」の模範解答を考えてみた - YouTube
そしてこの記事では動画の内容をざっくりとピックアップしてみました。
というわけで、動画が本編でこちらの記事はオマケになるので、できるだけ動画の方から先に視聴するようにしてください。
ちなみに動画の長さは35分程度です。
さくっと視聴するために1.5倍速ぐらいで視聴することをオススメします。
議論の前提
「コミットの粒度」という話題は言語やフレームワークを問わずに議論できるはずですが、念のため僕が普段開発している環境をお知らせしておきます。
- 使用する変更管理ツール=gitとGitHub
- 使用する言語とフレームワーク=RubyとRuby on Rails
- 対象アプリケーション=仕事で開発するRailsアプリケーション
上記の環境に近い人ほど、以下の議論が伝わりやすいかもしれません。
それでは以下が議論の本編(動画の大まかな内容)です。
SonicGardenのメンバーに聞いてみた(02:00~)
僕一人の意見に偏るといけないので、僕が勤めているソニックガーデンのメンバー(全員Railsプログラマ)に「適切なコミット粒度」について質問してみました。
以下はメンバーから出た意見です。
意見1:チケット単位、または戻したい単位でコミット
- 機能単位(1タスク、1チケット)で1コミットする
- 大きなチケットはブランチを分けて、squashを使って1コミットにまとめる
意見2:コミットはドラクエのセーブと同じ
- ドラクエ=ためた経験値を失いたくないと思ったらセーブ
- 開発=ここまで書いたコード、ここまで作った機能を失いたくない、と思ったらコミット
意見3:正常に動く単位にする
- コミットした時点ではプログラムがちゃんと動く、テストコードが全部パスする状態に保つ
- ロールバックするケースもあるので、ロールバックしたときにプログラムが壊れている、というのは運用しづらい
意見4:TODOリスト単位でコミットする
- 大きな機能を実装する前はTODOリスト(例:カラムを追加する、計算ロジックを実装する、画面を作成する、etc)を作成する
- TODOリストのTODOを1つ終わらせるたびに1コミットする
意見5:コミットの前に機能ブランチを作る
- コードの変更管理の単位はコミットだけではない
- コミットよりも一段大きな単位として、ブランチとマージ(pull request)という単位もある
- 大きな機能を実装する場合は、ブランチを切って「機能実装に着手する前にロールバック」できるようにしておくことも大事
極端な「もしも」を考えてみる(10:50~)
「粒度」というのは簡単にいうと「大きさ」のことです。
適切な大きさを考えるのであれば、「極端に大きい粒度」と「極端に小さい粒度」を想像してみると、両者のデメリットから「適切な粒度」が見えてきます。
極端に大きかったら?
まったくコミットしなかったら?もしくは、リリース直前に1回だけコミットするとしたら?
- コミットしない=元に戻れない
- 「何かあったらここまで戻りたい」「この変更を加えたらプログラムが壊れるかもしれない」と思ったときにコミット
- 最悪、プログラムが壊れたときにはロールバックできるようにする
極端に小さかったら?
もし1行毎にコミットしたら?
- コミットログが大量に増える
- コミットメッセージの付け方にも困りそう
- コミットのdiffを見たときに、他の行や他のファイルの変更点もある程度見えないと、どういう目的でどういう変更がなされたのか把握しづらい
粒度を考えてみるとわかる、コミットの役割
- 何か問題が起きたときに「ここへ戻りたい!」というポイントを保存する
- コミットはあとから変更内容を振り返るときにも使われる
- blameして「この行はどういう目的で書かれたんだろう?」と振り返るときにも使われる
その他の流儀(16:40~)
僕は必ずしも適用しているわけではありませんが、こういう考え方もあります。
generatorで自動生成したら即コミットする
- generatorで自動生成したら即コミット、そのあと、手で書き換えたら再度コミット
- どれが自動生成されたコードで、どれが人間の手で書いたコードなのかがあとから分かる
- ただし、すでに途中まで手で変更を加えている状態で、generatorを使うと結局手で変更したコードが混じってしまう(し、それだとプログラムが壊れた状態でコミットすることにもなりかねない)
実際のコミットログを見てみる(19:20~)
最後に、実際のコミットログを見ながら、僕がどんな粒度で、どんな考え方でコミットしているのかを自分で考察してみました。
ここで使ったのは以下のコミットログです。
- rails newしたあとの初回コミット
- モデルを追加
- 改札口モデルに降車メソッド(
exit?
メソッド)の正常系を実装 - 降車メソッドの異常系(運賃不足の場合)を実装
- 切符モデルに降車駅を記録するロジックを実装
- 画面(ControllerとView)の作成
- 画面周り(見た目)の手直し
まとめ
というわけで、いろいろ考えてみた結果、以下のようなタイミングでコミットするようにすると、そこそこ「適切な粒度」でコミットできるようになるんじゃないでしょうか?
- 何かあったらここまで戻りたい!という単位でコミット
- あとから振り返ったときに「変更の意図や目的」がわかる単位でコミット
- 正常に動く単位でコミット
- ロールバックしたらプログラムが壊れた!とならないように
- TODOリストのTODOが1つ片付いたらコミット
- 大きな機能を実装する場合はまず機能ブランチを切って、最後にマージ(pull request)する
- 場合によっては複数のコミットを1コミットにsquashする
- Rails本体の開発など
こうした内容を参考にしつつ、みなさんの開発の現場でも「適切なコミット粒度」について議論してみてください!