「良いコード」の次は「良いコミュニケーション」だね
コミュニケーション……毎日居酒屋で一杯やりながらってやつですね!?
そういう意味じゃないって。一緒のチームで開発するうえで大事なのは、それぞれのメンバーがどういう仕事をしているのか、この部分は誰がどうやって作ったのか、これは何のためにあるのか、そういった開発に関するさまざまな情報を適切に共有すること だよ
Redmine[1] とメールじゃだめなんですか?
それらももちろん重要だけど、VCS自体もコミュニケーションのためのツールなんだよ
えっ、VCSでコミュニケーションするんですか!?
ああ。VCSの使い方の最小単位といえば「コミット」だけど、そこにどんな情報を込めるかによって、意図の伝わりやすさ は大きく変わるんだ。VCSは、使いかたによって宝の山にもゴミの山にもなるんだよ
情報価値の高いコミットメッセージを書こう
コミットメッセージは、開発を継続していくうえでとても貴重な情報源になります。しかし、初級者、あるいはそのプロジェクトに明るくない人は、情報価値の低いコミットメッセージを書いてしまいがちです。たとえば、図1 はあるコミットの詳細をgitshow
で表示したもので、コミットメッセージには「setTimeout
を使うようにした」と書かれています。しかし、「 setTimeout
を使ったこと」はソースコードの変更内容から明らかであるため、ここでは重要ではありません。ほかの開発者がこのコミットを見たとき、本当に得たい情報は「setTimeout
という関数を使って『何をしたかったのか』 」ではないでしょうか。
図1 情報量が少ないコミットメッセージ
commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Author: Taro Yamada <[email protected] >
Date: Fri Dec 19 14:58:05 2014 +0900
Use the function 'SetTimeout'
diff --git a/content/messenger-overlay.js b/content/messenger-overlay.js
index 9035704..cc54aa0 100644
--- a/content/messenger-overlay.js
+++ b/content/messenger-overlay.js
@@ -6,5 +6,8 @@
document.addEventListener("DOMContentLoaded", function onDOMContentLoaded(aEvent) {
document.removeEventListener("DOMContentLoaded", onDOMContentLoaded);
document.documentElement.style.visibility = "hidden";
+ setTimeout(function () {
+ document.documentElement.style.visibility = "";
+ }, 1000);
});
})();
このように、有効な追加情報が得られない、情報量が少なすぎるコミットメッセージはよくあります[2] が、本章ではこのようなコミットメッセージのことを情報価値が低い と表現することにします。
コミットメッセージの情報価値
setTimeout
という関数を使って「何をしたかったのか」 。それを把握するためには、このコミットの背景を知る必要があります。
これは、第3章や第4章でも例に挙げたアドオンtbforce-auth-at-startupの開発中のコミットです。図1におけるdocument.documentElement.style.visibility= "hidden";
の個所が「UIを非表示にする」という処理です。setTimeout
の個所は「1秒後にUI要素を表示にする」という処理ですが、「 認証成功」の動作が抜けていることからわかるように、これ自体はアドオンの目的である「Thunderbirdの起動時にUIを非表示にしたあと、すべての受信サーバへの認証を試行して、成功した場合だけUIを表示する」を直接的に達成するものではありません。
実はこのコミットは、「 実際にそのようなアドオンを開発できるのか?」という要素術の検証段階で行われたものでした。具体的には次のような場面です。
UIを非表示にできることが実際に確認できた
UIを再表示できるかを実験するため、ひとまず1秒後に再表示処理を行ってみる
このような事情は、コードからは読み取ることができません。そのため、ドキュメントやコメントなどで説明を補う必要があります。しかし、コミットメッセージもまた、そのような情報を書き記すための場所として使うことができます。
以上を踏まえると、適切なコミットメッセージは、たとえば図2 のようなものになるでしょう。本章ではこのようなコミットメッセージのことを情報価値が高い と表現することにします。
図2 適切なコミットメッセージ
Show UI again with a delay to confirm its visibility is certainly restorable
(表示状態をもとに戻せるかどうかを検証するために、一定時間の遅延のあとでUIを再表示する)
情報価値の高いメッセージを書くコツ
情報価値の高いコミットメッセージをなかなか書けないという場合にはどうすればよいでしょうか。
コミットメッセージの価値を高めるためには、まずコミットメッセージ以外では記録できない情報を示す ということ、そして適切な文脈で変更内容をとらえる ということが重要です。図2に示した適切なコミットメッセージの例も、その点を強く意識して書かれています。具体的にどういうことなのか、それぞれを詳しく見ていきましょう。
コミットメッセージ以外では記録できない「Why」を書こう
コミットメッセージに書くべき内容は、コメントに書くべき内容と似ています。
コードを読んですぐにわかることは、コメントに書かない
コメントには、コードを読んでもわからない周辺事情などを書く
具体的な内容としては、ニュース記事の書きかたや、何かを報告するときなどの、5W1Hを考えるとよいです。つまり、
Who(誰が)
What(何を)
When(いつ)
Where(どこで)
Why(なぜ)
How(どのように)
が明示されていると、わかりやすく情報価値の高い記事と言えます。
コミットしたソースコードの差分そのものは、「 どのように変更したか」を表現した、そのコミットの主題と言えます。これは、5W1Hでは「How」に相当します。
コミットに含まれるそれ以外の情報はすべて、その主題についての補足説明ですが、VCSは補足説明をある程度自動的に補ってくれます。具体的には、誰がいつコミットしたのかはコミットログの情報として自明なので、「 Who」「 When」は、書く必要がありません。また、変更したファイル名や関数名などは差分から読み取れるので、「 What」「 Where」も書かなくてよい場合が多いです。
そうすると、残るは「Why」なのですが、これはVCSは自動的には補ってくれません。ここまでに述べた情報やコードからも読み取ることのできない情報なので、ここは自分で書かなくてはいけません。よって、コミットメッセージを書くときは「Why」を主軸に置いて考えることが有効 だと言えます。この観点から、図2で示した適切なコミットメッセージの例でも「表示状態をもとに戻せるかどうかを検証するため」という「Why」を強調しています。
コミットメッセージは自由記入欄なのでなんでも書けますが、そこでなくても書けることよりは、そこでなければ書けないことを書くように意識しましょう。ほかのことを見ればわかるようなことしか書かれていないと、情報価値の低いコミットメッセージになってしまいがちです。
変更の文脈を意識しよう
適切なコミットメッセージとは、その変更が本質的にどういうものなのかを説明するものです。コミットにも文脈があり、どんなに詳しい説明でも、文脈が適切でないと意味をなしません。
図1のコミットで行われている変更も、どの文脈の話ととらえるかによって次のようにいろいろな説明ができます。
JavaScriptだという点に着目すると「setTimeout()
を使うようにした」
DOM (Document Object Model )を使っているという点に着目すると「DOMを使って要素の表示/非表示を切り替えた」
GUIアプリケーションという点に着目すると「UIの表示/非表示を切り替えた」
メールクライアントという点に着目すると「起動直後に禁止した操作を、再び許可するようにした」
開発初期の試験実装という点に着目すると「状態を戻せるかどうかを検証するために、一定時間後に状態を戻すようにした」
図1のようなコミットをしたのは、「 RubyでWebサービスを開発した経験はあるが、jQueryを使わないJavaScriptでの開発はあまり経験がなく、Thunderbird用のアドオンの開発も初めて」という人でした。その経験と照らし合わせると「JavaScriptである」ということだけに意識が向いてしまうのも無理はありません。ただ、その文脈のみでの説明は事実に反してはいなくても、事実のあまりに狭い一面しか言い表せていません。
このような場合は、コミットする前にまずひと呼吸置くとよいでしょう。1つのことに集中していると、ほかのことが見えなくなりがちです。自分が書こうとしているメッセージがどの文脈の話なのかを、いったん退いて客観的な視点で見なおしてみましょう。「 今何をしていたのか」から「そもそも、何のためにそれをしようとしていたのか」に焦点を移すと、多面的でより適切な説明ができる文脈 に気がつきやすいです。
実際に、図2で示した適切なコミットメッセージの例では、「 GUIアプリケーションの」「 開発初期の試験実装である」という文脈に沿って、「 Why」ならびに実際にやっていることの意味合いを示しています。また、Thunderbirdのアドオンという文脈を考えると、開発に使っている要素技術そのものについての言及は不要と考えられるため、関数名やAPIそのものには言及していません。
「Why」以外の情報を付け加える
「Why」をコミットメッセージに含めるだけではまだ伝わりにくい情報もあります。そういった情報を補完するためのコミットメッセージの例をいくつか示しましょう。
変更前後の挙動をbefore:とafter:で説明する
条件分岐の条件式を変えたり、呼び出すメソッドを変えたりといった変更は、変更に伴う影響範囲が一見してわからないことがあります。
このような場合には、変更前と変更後で挙動や処理結果にどのような違いが出るのかを次のような形式で書いておくと、コミットの意図を理解しやすくなります。
before:
ここで変更前のふるまいを説明する
after:
ここで変更後のふるまいを説明する
境界値付近[3] での振る舞いの変化などが説明されていると、あとから参照するときの資料にもなります。
どこを誤記したのかを説明する
誤記の修正で、「 Fix a typo」というコミットメッセージにすることはよくあります。しかし、もう一歩進んでどこを誤記したのかを説明しておくと、コミットを見た人が「どこが変わったのか」を理解する手助けとなります。
たとえば、「 chain」と書くべきところを、うっかり「chian」と誤記していたものを修正したとしましょう。単純にこの変更の前後を示そうとすると、次のように横に並べることを思いつくかもしれません。
chian -> chain
しかし、このようによく似たフレーズは、横に並べても違いがぱっと見でわかりにくいです。こういったものは、次のように変更を縦に並べたほうが見やすい です。また、この例ではさらに、^^で行中の何文字目を間違えていたかを強調しています。
chian ->
chain
^^
実行したコマンドを書く
複数ファイルに渡って一括置換するときに、慎重に用意したワンライナー[4] を実行することがあります。また、バージョン番号を繰り上げたり、リリースしたり、テンプレートに基づいて新しいコードを生成させたりといった定型的な作業を自動化してある場合に、何かコマンドを実行すると複数のファイルが一気に書き換わることがあります。
たとえば、バージョン番号を繰り上げるmake
のルールが定義済みで、新バージョンをリリースしたあとは必ずmake update-version NEW_VERSION=4.1.1
といったコマンドでバージョン番号に関係するすべてのソースを更新するようになっているとしましょう。
そのあと、コミットするときに何も補足情報がなく単に「Bump version to 4.1.1」( バージョンを4.1.1に繰り上げた)というコミットメッセージだけを添えていた場合、あとからそのコミットを見た人は、それが自動処理で行われたものなのか手作業で行われたものなのかを知ることができません。そのため、似た作業を別の人がしようとしたときに、無駄に手作業でやってしまい、その結果見落としが発生してしまうかもしれません。
そのような事態の発生を防ぐためにも、何かのコマンドの実行結果をそのままコミットするときは、コミットメッセージに実行したコマンドを含めておきましょう。そうすることで、何を実行してその差分が発生したのか がわかりやすくなります。
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行だけの変更をコミットすることも珍しくありません。
逆に言うと、細かい粒度での開発を心がけていれば自然とコミットの頻度は高くなる 、ということでもあります。これまでの章の内容も踏まえると、「 名前付けのしやすさ 」「 実装の粒度の適切さ 」そして「コミットの頻度の高さ(こまめなコミットのしやすさ) 」は互いに相関していると言えるでしょう。
ついでの修正をまとめてコミットしない
規模の大きなコミットの別パターンとしてよくあるのが、ついでコミット です。
リスト1 は、引数が何も指定されていなければ「Hello,world!」と出力してすぐに終了し、そうでなければ後続の処理をするという実装です。これに対して、処理の最後にログを出力するようにしたのがリスト2 です。その際、if文の条件の丸括弧と波括弧の間にスペースがないことに気づいて修正しており、また、もともと呼び出していた初期化のための関数の名前を間違えていたことにも気づいて、こちらも併せて修正しています。
リスト1 変更前のコード
function startup() {
if (arguments.length > 0){ // ここだけ波括弧の前にスペースがない
console.log("Hello, world!");
return;
}
initalize(); // 関数名が間違っている
process();
}
リスト2 変更後のコード
function startup() {
if (arguments.length > 0) { // 波括弧の前にスペースを挿入した
console.log("Hello, world!");
return;
}
initialize(); // 誤記を訂正した
process();
outputLog(); // ログ出力の関数を呼ぶようにした
}
...
function outputLog() {
// ログ出力の処理をここで実装した
}
この状態で変更をコミットすると、3つの変更がすべて含まれたコミットになってしまいます。これが「ついでコミット」です。
事情を知らない人がこのコミットを見たら、「 波括弧の前のスペースもログ出力のために必要だったのか?」と思われてしまいかねません。また、「 ログ出力をやめよう」と思ってこのコミットを取り消すと、誤記の修正まで巻き添えを食って取り消されてしまいます。そのことに気づかないまま作業を続けてしまうと、直したはずだった誤記のせいで再び問題が発生して、原因究明に余計な手間がかかってしまう、ということも起こり得ます。
このようにいくつもの弊害があるので、「 ついでコミット」は原則として避けたほうがよいです。
ミスやスタイルを修正するコミット
ついでコミットをしないためには、コーディングスタイルを直したら「Fix coding style」とコミット、誤記を見つけたら「Fix typo」とコミットするという風に、どんな細かな修正でもその都度コミットする ようにすることが大切です。
「恥ずかしいミスだからこっそり直したい」と考える人もいるかもしれません。しかし、ミスをごまかしたいという動機で1トピックごとに1コミットというルールを曲げてはいけません。恥ずかしがらずに堂々と、個別の変更としてコミットしましょう[5] 。
また、コーディングスタイルを修正するコミットのときは、インデント幅の修正であればインデント幅だけ、改行位置の変更であれば改行位置だけという風に、コーディングスタイルの個々の項目ごと にコミットすることも大切です。そうすることで、コミットメッセージもより簡潔なものになります。たとえば、このような場面でよく使われるコミットメッセージには次のようなものがあります。
Align:連続する代入文の右辺などの縦の位置をそろえたとき
Indent:インデント幅を増やしたとき
Unindent:インデント幅を減らしたとき
Fix indent:一部の行だけのインデント幅が崩れたものをそろえたとき
Tabify:ソフトタブをハードタブで置き換えたとき
Untabify:ハードタブをソフトタブで置き換えたとき
改行位置や空白の空けかたなどの修正については、簡潔に言い表せる表現がないため「Fix coding style」というコミットメッセージが使われることもあります。
--patchオプションを使って分割しよう
ただ、気をつけていても、「 つい熱中して、一気にやってしまった」「 ついつい、一緒に修正してしまった」ということはやはり発生しがちです。そんなときでも、変更を個別に分割 してコミットできます[6] 。
具体例として、リスト1のコードをリスト2のように変更するコミットの場合を考えましょう。
git add
コマンドまたはgit commit
コマンドに--patch
オプション(短縮形は-p
)を指定すると、あとからでもそれぞれの変更を分割してコミットできます。具体的には、Unified Diff[7] の一つ一つのhunk[8] ごとにコミットするかどうかをインタラクティブに選択できます。図4 は、リスト2の変更を行った状態でgit add -p
したときの様子です。
図4 ブロック単位のdi
diff --git a/sample.js b/sample.js
index f649872..d64a4bf 100644
--- a/sample.js
+++ b/sample.js
@@ -2,7 +2,7 @@
function startup() {
- if (arguments.length > 0){
+ if (arguments.length > 0) {
console.log("Hello, world!");
return;
}
- initalize();
+ initialize();
process();
+ outputLog();
}
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?
図4には、このブロックの変更点として「コーディングスタイルの訂正」「 誤記の訂正」「 関数呼び出しの追加の変更」の3つすべてが表示されていることがわかります。このとき取れる操作にはいくつかの選択肢がありますが[9] 、代表的なものは次のとおりです。
y(yes、はい)
n(no、いいえ)
s(split、分割)
e(edit、編集)
y
を選択するとそのブロック全体がコミットに含められます。逆に、n
を選択するとそのブロック全体がコミットから除外されます。多くの場合は、この2つを使ってブロック単位で変更をコミットに含めるかどうかを決めることになります。
s
を選択すると、ブロック内の複数の変更個所がさらに図5 のように細かい単位に分割されて、再びそのブロックへの操作を尋ねられます。今回のように1つのブロックの中に複数の変更が含まれてしまった場合は、この方法でブロックを小さくしてから、個別にy
またはn
を選択することで必要な個所だけをコミットできます。
図5 小さなブロックに分割されたあとのdi
diff --git a/sample.js b/sample.js
index f649872..d64a4bf 100644
--- a/sample.js
+++ b/sample.js
@@ -2,7 +2,7 @@
function startup() {
- if (arguments.length > 0){
+ if (arguments.length > 0) {
console.log("Hello, world!");
return;
}
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?
また、e
を選択すると、テキストエディタ(Vimなど)を使って、コミットに含める変更を行単位で編集できます。この場合には、diffの行頭の「-
」を半角スペースに置き換えて行の削除を取り消したり、行頭が「+
」である行を削除して行の追加を取り消したりできます。s
ではどうしても思ったとおりの単位に変更個所が分割されない場合の最終手段と言えます。
これらの選択肢を使い分ければ、複数の変更が入り交じったコミットを個別のコミットに分けられます。変更をコミットするときにgit add -p
やgitcommit -p
を使うクセを付けておくと、うっかり複数の変更を一度にコミットしてしまうことを防げます。
間違えたコミットはrevertで取り消そう
開発を進めていると、メソッドを間違ったクラスに実装してしまったり、まだコミットするつもりではなかった作業中のファイルをコミットしてしまったりといった間違えたコミット が発生することがあります。また、試行錯誤しながらの開発では、試しに実装してみたものの、結果が芳しくなかったのでもとに戻したい、ということもよくあります。
Gitでは、以前のコミットを取り消す内容のコミットをgit revert
で生成できます。たとえば直前に行ったコミットは、git revert HEAD^
[10] で取り消せます。
特定のコミットを取り消す場合は、git revert d3eadd6b231275dfb7bf0b11d9fc1d3b6321c3d1
のようにコミットIDを指定します。コミットIDはgit log
などであらかじめ確認しておきます。
git revert
を実行すると、通常のコミットと同様に、エディタが起動してコミットメッセージを編集できる状態になります。通常のコミットと異なるのは、どのコミットを取り消すコミットであるのかを示す図6 のようなメッセージがあらかじめ入力された状態になっていることです。
図6 変更を取り消すコミットの既定のメッセージ
Revert "Add a feature A"
This reverts commit d3eadd6b231275dfb7bf0b11d9fc1d3b6321c3d1.
このままコミットしてしまうこともできますが、単に「~のコミットをrevertする」というメッセージでは、ほかの開発者が読んだとき、なぜそれが間違いであったのかを知ることができません。図7 のように、変更を取り消す理由をメッセージに加えておきましょう。
図7 変更を取り消す理由を追加したコミットメッセージ
Revert "Add a feature A"
This reverts commit d3eadd6b231275dfb7bf0b11d9fc1d3b6321c3d1.
Because it conflicts with feature B.
コミットに成功したら、最後に、共有リポジトリに変更を反映するためにgit push origin
を実行します。これで、コミットの取り消しは完了です。
revert
は「指定されたコミットで行われた変更をまたもとに戻すコミット」を自動的に行う操作です。ですので、コミットログには「間違えたコミット」と「それを取り消したコミット」の両方が記録されることになります。
良いコミットをすればコミットログを活用できる
バージョン管理システムにはさまざまな機能がありますが、それらを活用するためには良いコミットを行う必要があります。どんなにコードそのものがきれいでも、コミットのしかたが良くないと、バージョン管理システムの真価は引き出せません。
コミットログは見返すためにある
適切にコミットメッセージが書かれていると、新バージョンをリリースする際の「前のバージョンからの変更点の一覧」を作成する作業が簡単になります。
バージョン番号を付与して新しいリリース版を提供するときには、前のバージョンから何がどう変わったのかを説明する必要があります。適切なコミットメッセージによって情報価値の高いコミットログになっていれば、コミットログからコミットメッセージを収集してくるだけで、それがそのまま「新しいバージョンでの変更点の網羅的な一覧」となります。
行単位でのコミットログで原因を調べる
行単位でのコミットログもまた、さまざまな場面で役立ちます。
たとえば、ある行が不具合の原因であるとわかったとき、そのコードを削除してもよいかどうかはどうすれば判断できるでしょうか? 一見すると奇異なコードでも、実はとても重要なコードで、それを削除するとほかにも大きな影響を与えてしまう、ということはよくあります。そのような場合、毎回すべての影響範囲を調べるわけにもいきません。
コミットメッセージが適切に書かれていれば、この場合、行ごとのコミットログが手がかりになります。Gitのリポジトリであれば、git blame
やgit show
というコマンドが役に立ちます。blame
はあるリビジョンのコードについて、行ごとにそれがどのコミットに由来しているのかを表示する機能です(図8 )( 注11 ) 。
図8 git blame
これを使用して障害の原因となっている行が導入されたコミットを探し出し、git show
でそのコミットメッセージや差分を読むことで、「 一見すると奇異なコード」が「どういう理由で導入されたのか」を知ることができます。調査の範囲を絞り込むための大きなヒントとなるでしょう。
しかし、情報価値の低いコミットメッセージや、複数の変更が混ざった大規模なコミットが多いと、行ごとの変更履歴は活用しにくくなってしまいます(図9 ) 。
図9 コミットログの情報価値が高いと、さまざまなことを
放っておくと実装が肥大化してしまうように、コミットも放っておくと肥大化してしまいがちだね。「 コミットの単位でコードを管理できる」というバージョン管理システムの利点を有効活用するには、良いコミットが欠かせないんだ。良いコミットを重ねてこそ、リポジトリは利用価値の高い宝の山になるんだよ
僕、コミットログって今まで見たことなかったです。もっと活用していかないともったいないですね……!
COLUMN 間違いを取り消すためにgit push -fしてはいけない
Gitでは、「 コミットを取り消すコミットをする」以外に、「 コミット自体をなかったことにする」こともできます。そうしてコミット自体をなかったことにしたうえで、操作を強制的に行う-f
オプションを添えてgit push -f
とコマンドを実行すると、ローカルリポジトリの内容をリモートリポジトリに強制的に反映して、「 間違えたコミット」の痕跡を完全に消してしまうことができます。
しかし、原則としてこの操作は複数人で作業している共有リポジトリに対して使ってはいけません。リポジトリを破壊してしまう恐れがあるからです。
通常、git push
はリモートリポジトリ側に新しい変更があった場合、先にそれらをpull
してから再度push
しなおすように警告して、処理を中断します。しかし、git push -f
は一切の確認なしに変更を反映します。もし仮にリモートリポジトリにほかの人が何か変更をコミットしていても、それらはすべて失われてしまうことになります。
このようなトラブルを避けるためにも、コミットの取り消しは必ずgit revert
で行うようにしましょう。