Git活用法 ー コードはいつも1行ごとにドキュメント化されている

コードには1行ごとに隠しドキュメントがあります。

次のコードスニペットの4行目を書いた人は、何か理由があってDOMノードのclientLeftプロパティにアクセスしたのでしょうが、結果的に何もしていません。これはかなり不可解です。なぜこうしたのか、あなたは説明できますか? 今後、この呼び出しを変更したり削除したりしても安全でしょうか?

// ...
if (duration > 0) this.bind(endEvent, wrappedCallback)

this.get(0).clientLeft

this.css(cssValues)

私ではなく他の人があなたにこのコードを見せたとして、誰がこの行を記述したのか、どんな理由があったのか、このままの状態にしなければいけないのか、あなたはおそらく説明できないでしょう。ただし、プロジェクトを進めているときは大抵の場合、バージョン管理システムを使ってその履歴にアクセスできます。

プロジェクトの履歴は最も有用なドキュメントです。

この行について説明している次のコミットメッセージを読むと、謎が解けます。

$ git show $(git blame example.js -L 4,4 | awk '{print $1}')

DOMに追加しただけの要素についてanimate()を修正
DOMに追加しただけの要素の場合、CSSのtransitionはWebkitとMozillaのどちらでも有効になりません。この問題の対処方法として、以前はCSSのプロパティをsetTimeoutで設定していました(272513bを参照)。

この方法でWebkitでの問題は解決しましたが、最新版のFirefoxでは解決しませんでした。Mozillaでは少なくとも15msのタイムアウトが必要のようですが、この値もさまざまのようです。

両方のエンジンにより適した解決策は、“layout”をトリガーすることです。この処理は要素からclientLeftを読み込むと実行されます。layoutをトリガーするプロパティやメソッドは他にもあります。gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkitを参照してください。

今にしてみると、この行(もっと具体的に言うと、この行について説明した変更)は、なぜこの行が必要だったのか、なぜ以前の方法(コミットSHAで参照する方法)は機能しなかったのか、どのブラウザが影響を受けているのか、そして詳細のためのリンクといった情報がだらだらとドキュメント化されています。

そして実は、この不可解な行を書いたのは私です。もっと良いコードを書く方法はありました。例えば、triggerLayout()のような目的が明らかな名前の関数に重要なプロパティアクセスをカプセル化することもできましたし、せめてコードのコメントを追加して、この行はアニメーションを開始するための行だと簡単に説明することくらいはできました。どういうわけか、私はその日、このコードを書くのに失敗したようです。コードを書いても、必ずしも完璧とは限りません。

たとえこのコードがもっと理解しやすいものであったとしても、あるいはコードのコメント行が記述されていたとしても、プロジェクトの履歴の方がずっと充実した情報を提供することができます。

  1. このコードを書いたのは誰か
  2. このコードが追加されたのはいつか
  3. 付随するテストはどれだったか(該当するテストがある場合)
  4. 完全なコミットメッセージはストーリーになります(一方、コードのコメントは簡潔に記述する必要があります)

もちろん、コードの品質はとても重要です。しかし、コーディングをさらにもっと改善することを考えるなら、より良いコミットメッセージを書くことを目指すべきです。これはあなただけが目指すのではなく、あなたのチーム全体そして担当者全員が目指す必要があります。ソフトウェアのストーリーというのは、その最新のチェックアウトと同じくらい重要です。

プロジェクトの履歴を効率的に調べる

git blame

コマンドラインからgit blameを使用する方法は上ですでに説明しました。Gitのローカルリポジトリにアクセスできない場合は、GitHub上で任意のファイルの“blame”ビューを開くこともできます。

ファイルの履歴を効率良く調べるには、VimとFugitiveを使用します。

  1. バッファで:Gblameを使用して、blameビューを開きます。
  2. さらに掘り下げる必要がある場合は、blameペインの行の上でShift-Pを押して、そのコミットの親でもう一度blameを実行します。
  3. oを押して、blameペインで現在選択されているコミットが表示されている分割ウィンドウを開きます。
  4. コミット分割ウィンドウで:Gbrowseを使用して、GitHub Webインターフェースでコミットを開きます。
  5. gqを押してblameペインを閉じて、メインバッファに戻ります。

git blame view in vim Fugitive

詳細については、:help Gblameを参照してください。

コミットが発生したプルリクエストを探す

git blameを使えば、変更内容を説明したコミットSHAを取得できるかもしれません。ただしコミットメッセージは、変更の根本的な理由を説明できるだけの情報やコンテキストを必ずしも伝えるわけではありません。しかし、チームがプロジェクトでGitHub Flowを実践している場合、プルリクエストのやり取りの中でコンテキストが見つかる場合があります。

$ git log --merges --ancestry-path --oneline <SHA>..origin | tail
...
bc4712d Merge pull request #42 from sticky-sidebar
3f883f0 Merge branch 'master' into sticky-sidebar

このように、pull request #42による変更だということがコミットSHA 1つだけで分かりました。

git pickaxe(つるはし)

もうなくなっているもの(例えば、もはやどこからも呼び出されない関数呼び出しなど)を探したい場合もあるでしょう。特定のキーワードが追加されたり削除されたりしたコミットを探す最も良い方法は、git logに‘pickaxe(つるはし)’引数を使うことです。

$ git log -S<string>

この方法で、特定の関数の呼び出しを削除したコミットや、特定のCSSのクラス名を追加したコミットを発掘することができます。

git churn

プロジェクトの履歴を利用すると、コミットを個別に確認できるだけでなく、変更のセットを全体的に分析できるので、有益な見解が得られます。例えば、git-churnはシンプルですが、git logをラップして、どのファイルが一番変更されているかについてステータスをまとめる便利なスクリプトです。次のコマンドは、過去6か月間にどのアプリ開発が重点的に行われたかを確認できます。

$ git churn --since='6 months ago' app/ | tail

ちなみに、このような分析はプロジェクト内の潜在的な技術上の問題を浮き彫りにすることもあります。特定のファイルがあまりにも頻繁に変更されているのは、ほとんどの場合、危険信号です。そのファイルのコードは頻繁にバグ修正が必要になっているか、もしくはそのファイルで行っている処理が大きすぎるため、もっと小さなユニットに分割する必要がある可能性があります。

同様の方法で履歴分析を行えば、コードベースの特定部分を最近開発していた担当者を確認することができます。例えば、アプリケーションのAPI部分に最も関与した人を確認するには、以下のコマンドを実行します。

$ git log --format='%an' --since='6 months ago' app/controllers/api/ | \
    sort | uniq -c | sort -rn | head

 109 Edmond Dantès
  13 Jonathan Livingston
   7 Ebanezer Scrooge

履歴の正しい作り方

今あなたが作成しているものはすべて、プロジェクトの履歴に登録されて、永遠に残されるということを頭に入れておいてください。一緒に作業している他の人たちにとって親切になるように(たとえ1人のプロジェクトであっても、3ヶ月後のあなた自身も作業者です)、コミットするときは以下の基本ルールに従います。

次の質問に答えてください。

  • この変更はなぜ必要なのですか?
  • この問題にどうやって対処しますか?
  • この変更に伴う、思わぬ影響は何ですか?
  • リンク(ディスカッションへの)を記載するようにしてください。
  • 関係ない変更を1つのコミットに含めない。他の変更を行ったファイルで、入力ミスが見つかったりコードのリファクタリングを少ししたりするかもしれません。でも直接関係が無い限り、メインの変更と一緒にそのような変更をするのは避けてください。

  • プッシュの前に必ず履歴をクリーンアップする。コミットがまだ共有されていない場合は、安全にrebaseを使うことができます。以下の履歴はFaradayプロジェクトで永遠に維持されていたかもしれませんが、2件だけのコミットに圧縮し、それらのメッセージを編集して、最初の段階でスクリプトのセットアップに問題があったことを隠しました。

messy git history before rebase

  • 関係ない変更を避けるための方法: 行ベースのコーディングスタイルにこだわると、隣接する行を変更しないで、リストの値を追加、編集、削除することができます。以下に例を示します。
  var one = "foo"
    , two = "bar"
    , three = "baz"   // Comma-first style allows us to add or remove a
                      // new variable without touching other lines

  # Ruby:
  result = make_http_request(
    :method => 'POST',
    :url => api_url,
    :body => '...',   // Ruby allows us to leave a trailing comma, making it
  )                   // possible to add/remove params while not touching others

どうしてこのコーディングスタイルにする必要があるのでしょう? git blameを使おうとする人のことをいつも考えるようにしてください。このJavaScriptの例で、コミット済みの値"baz"を追加したのがあなただったとしたら、誰かが"bar"を追加した行でblameを使ったときに自分の名前が表示されるのは嫌ですよね。この2つの変数に関連性はないかもしれないからです。

ボーナススクリプト

ここまで読んでもらったお礼に、おまけのスクリプトをお見せしましょう。私はこれにgit-overwrittenという名前を付けて、任意のブランチで変更または削除した行の元の担当者についてblameの情報を表示しています。

$ git overwritten feature origin/master
  28 2014-02-04 1fb2633  Mislav Marohnić: Add Makefile for building and testing
   1 2014-01-13 b2d896a  Jingwen Owen Ou: Add -t to mktemp in script/make
  17 2014-01-07 385ccee  Jingwen Owen Ou: Add script/make for homebrew build

これはGitHub Flowでプルリクエストを開くときに便利です。同僚に自分のプルリクエストを確認してほしくても、誰にメッセージを送ればいいか分からない場合があります。git-overwrittenを使えば、あなたが変更した行を書いた元の人たちの名前が分かるので、プルリクエストを開くときに相手先を把握できます。