1年目から身につけたい! チーム開発 6つの心得

第5章 情報価値の高いコミットをしよう―コードだけではわからない意図を伝える技術

この記事を読むのに必要な時間:およそ 9.5 分

1つのコミットの意図は1つだけに絞ろう

コミットの意図のわかりやすさはコミットメッセージに左右される部分が大きいですが,そもそもコミットの規模自体が大きいために意図がわかりにくいということもあります。複数の意図が混ざったコミットはその代表的な例です。次のようなパターンは,その中でもよくある例です。

  • 熱中して一気に進めすぎてしまった
  • ついでの修正をまとめてしまった

熱中して一気に進めすぎない

図3は,熱中して一気に進めすぎてしまったコミットの例です。これは「Thunderbirdで表示したメールの本文にあるリンクについて,特定のパターンに当てはまるものはInternet Explorerで開き,別のパターンに当てはまるものはGoogle Chromeで開く」というアドオンの開発初期段階のコミットです。まだ手探りでの開発だったため,⁠やりたいことが本当に可能か試行錯誤しつつ実装し,意図どおり動いたのでコミットした」という状況でした。

図3 一気に進めすぎてしまったコミット

Open clicked link by IE
------------------------------------
diff --git a/content/messenger-overlay.js b/content/messenger-overlay.js
index xxxxxxx..xxxxxxx 100644
--- a/content/messenger-overlay.js
+++ b/content/messenger-overlay.js
@@ -5,17 +5,38 @@
  (function (aGlobal) {
  const Cc = Components.classes;
  const Ci = Components.interfaces;
  const kIEPath = "C:\\Program Files\\Internet Explorer\\iexplore.exe";
  const kChromePath = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe";
  var SwitchLinkExternalHandler = {
-    run: function run() {
+    startIE: function startIE(aURL) {
+      this.startExternalProcess(kIEPath, aURL);
+    },
+    startChrome: function startChrome(aURL) {
+      this.startExternalProcess(kChromePath, aURL);
+    },
+    startExternalProcess: function startExternalProcess(aPath, aURL) {
+      var process = Cc["@mozilla.org/process/util;1"]
+                      .createInstance(Ci.nsIProcess);
+      var file = Cc["@mozilla.org/file/local;1"]
+                    .createInstance(Ci.nsILocalFile);
+      var args = [aURL];
+      file.initWithPath(aPath);
+      process.init(file);
+      process.run(false, args, args.length);
+    },
+    run: function run(aURL) {
+      this.startIE(aURL);
     },
  };
  
+  var browser = document.getElementById("messagepane");
+  browser.addEventListener("click", function onClick(aEvent) {
+    let href = hRefForClickEvent(aEvent);
+    if (href.match(/^https?:/)) {
+      SwitchLinkExternalHandler.run(href);
+      aEvent.preventDefault();
+      aEvent.stopPropagation();
+    }
+  }, true);
  aGlobal.SwitchLinkExternalHandler = SwitchLinkExternalHandler;
  })(this);

このコミットでは,次の4つのことを一度に行っています。

  • ユーザのクリック操作を検知するためのイベントリスナを設定した
  • 外部のアプリケーションを起動するためのSwitchLinkExternal Handlerを実装した
  • Internet Explorerを起動するためのstartIEを実装した
  • Google Chromeを起動するためのstartChromeを実装した

しかし,これらの変更すべてを一言でスッキリと言い表すのは難しいです。そこで,この段階で実現されている「クリックされたリンクがInternet Explorerで開かれるようになった」という挙動に基づいて,⁠Open clicked link by IE」というコミットメッセージにしたというわけです。

以上の経緯を見ると,このコミットが「1つのコミットの中で多くのことをやりすぎている」のは明白です。コミットメッセージとして「どのように変更したか」をスッキリ言い表せなかったために苦し紛れのコミットメッセージを付けてしまい,そのため,コミットメッセージの内容とまるで関係がないGoogle Chrome用の実装がコミットに含まれているというちぐはぐな状況になってしまっています。

コミットを分ける適切なタイミング

どこまで作業を進めれば,1つの変更としてコミットするのにふさわしいのでしょうか? これもやはり,コミットメッセージとして「Why」を書きやすい間隔というのが1つの指標と言えるでしょう。先の例も,行った4つの変更それぞれが個別のコミットになっていてよかった場面です。

「ひと区切りついたらコミットする」というのは一般的な基本方針ですが,作業に熱中していると,その「区切り」を無意識のうちに逃してしまいがちです。そのような性格の人は,意識して細かい間隔にするように気をつけましょう。クリアコード内では,1時間に1回もコミットがない場合は,一気に進めすぎているか悩みすぎているかのどちらかである可能性が高いということで,注意喚起がなされることもあります。

とはいえ,時間的な間隔だけにとらわれないことも大切です。具体的には「1時間ごとにコミットする」⁠午前中の作業のまとめとしてコミットする」のような基準での区切りかたは避けるべきです。そこに意識が集中してしまうと,⁠まだ1時間経っていないから」のようにコミットの時機を逸してしまったり,⁠まだ不完全だけど時間が経ったから」のように変更の単位として意図を読み取りづらいコミットになってしまいます。コミットはあくまで,変更の「内容」を主題として行うのが望ましいです。そのような基準で考えると,1行だけの変更をコミットすることも珍しくありません。

逆に言うと,細かい粒度での開発を心がけていれば自然とコミットの頻度は高くなるということでもあります。これまでの章の内容も踏まえると,名前付けのしやすさ実装の粒度の適切さそして「コミットの頻度の高さ(こまめなコミットのしやすさ)⁠は互いに相関していると言えるでしょう。

著者プロフィール

足永拓郎(あしえたくろう)

株式会社クリアコード所属。デスクトップや組み込みソフトウェアの自由を求めて活動中。


結城洋志(ゆうきひろし)

株式会社クリアコード所属。Firefox黎明期からアドオン開発を手がけ,業務上もMozilla製品の技術サポートを担当。代表作は「ツリー型タブ」「テキストリンクなど。また,日経Linux誌にて「シス管系女子」を連載中。


林健太郎(はやしけんたろう)

株式会社クリアコード。趣味であれこれと,Sylpheedのプラグインを開発したり,Debianのパッケージをいくつかメンテナンスしている。

Twitter:@kenhys

コメント

コメントの記入