GitベースのコードリーディングTips

こんにちは、投稿推進部の森川 (@morishin127) です。

エンジニアが既存のプロダクトの開発に携わる際、他人の書いたソースコードを読み解くところから始まります。過去に書かれたコードの意図を理解することは自分が書いたものでもしばしば難しく、他人が書いたものならなおさらです。この記事では過去に書かれたコードを理解するための工夫についてお話したいと思います。

なお、この記事ではプロダクトのソースコードはgitおよびGitHubのPull Requestを利用して開発が進められていることを前提としています。

特定の行から関連するPull Requestページを開く

クックパッドのソースコードには概してコメントがあまり書かれておらず、見ただけでは理解しづらいような特殊な方法をとっている場合のみコメントを書いている印象です。基本的に実装に関する説明はソースコード中ではなく、GitHubのPull Requestページに書かれていることが多いです。

そのためコードの意図を知りたい時にはその箇所の変更に関するPull Requestのページを見に行きますが、そのページを検索するのは少々面倒です。そこでコード中の特定の行からその変更に関わるPull Requestページに直接的に辿り着く方法をご紹介します。

特定のコミットハッシュからそのコミットを含むブランチのマージコミットを見つけるこちら(StackOverflow)の方法が社内でシェアされていました。マージコミットが分かればそのコミットログに含まれるPull Request番号からPull RequestページのURLを生成することができます。これをシェルスクリプトの関数にしたものが下記になります。(※一部Rubyが混入しています)

open-pull-request () {
    merge_commit=$(ruby -e 'print (File.readlines(ARGV[0]) & File.readlines(ARGV[1])).last' <(git rev-list --ancestry-path $1..master) <(git rev-list --first-parent $1..master))
    if git show $merge_commit | grep -q 'pull request'
    then
        pull_request_number=$(git log -1 --format=%B $merge_commit | sed -e 's/^.*#\([0-9]*\).*$/\1/' | head -1)
        url="$YOUR_REPO_URL/pull/${pull_request_number}"
    fi
    open $url
}

$YOUR_REPO_URLの部分にはhttps://github.com/<owner>/<repo>を設定してください。

(2015-11-17 17:05 追記)
こちらのツイートで言われているように、hubコマンドをインストールしている方は$YOUR_REPO_URL`hub browse -u`に置き換えるのが良さそうです。

詳細を知りたい行でgit blameしてコミットハッシュを割り出せば、上記の関数を用いて該当のPull Requestページを開くことができます。

open-pull-request <commit hash>

Xcodeプラグイン

私は業務でXcodeを用いることが多く、上記のコマンドを実行するためにエディタからシェルに移りgit blameするのを煩わしく思ったためXcodeのプラグインにしました。Xcode上でショートカットキーを入力するだけで現在カーソルのある行に関連するPull Requestページを開くことができます。プラグインは新規に作成したのではなく近い機能を提供していたlarsxschneider/ShowInGitHubをForkしてその一機能として実装しています。

morishin/ShowInGitHub
https://github.com/morishin/ShowInGitHub

morishin/ShowInGitHubをcloneして手元のXcodeでビルドすると~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/ 以下にプラグインが配置されるのでXcodeを再起動すると有効になります。

最初に実装された時のコミットまで遡る

コードリーディングをする際にgit blameは強力です。blameすることで特定の行が変更された最新のコミットが割り出せ、そこからコミットメッセージやPull Requestの情報を辿ることができます。しかし最初の実装時の情報を知りたいのにその後にバグ修正などが行われ変更されていると、最初に実装された際のコミットが分からず困る場合があります。そこでtigというコマンドラインツールを用いるとblameを繰り返し簡単に過去に遡ることができます。

jonas/tig
https://github.com/jonas/tig

git blameと同じようにコマンドラインからtig blame <ファイル名>を叩くとコミット情報が表示されるので、さらにblameしたい行を選択して,キーを押すとその行が最後に変更されたコミットを起点として再度blameした結果が表示されます。これを繰り返すと簡単に目的のコミットを発見することができます。

古いコミットのコードを実行する時のgit-new-workdir

過去のコードを読むだけでなく、実際に実行して動作を確認したい場合は古いコミットをgit checkoutしてプログラムを実行します。昨今多くのプロダクトではパッケージマネージャを利用しており、checkoutの後にbundle installpod installといったパッケージのインストールを実行する必要が多いかと思います。そして確認を終えて最新のコミットに戻ってきた際にも再びインストールを実行する必要があり、長い時間パッケージのインストールで待たされることになります。

このような場合はgit-new-workdirというコマンドを使って新たな作業ディレクトリを作成するとディレクトリ毎に別々のコミットをcheckoutできるので、最新にcheckoutしたディレクトリはそのまま置いておいて別の作業ディレクトリで古いコミットの動作確認をすることができます。git-new-workdirgit-core/contrib/workdir/git-new-workdirに入っているコマンドです。ちなみにgit-new-workdirの存在はCTO氏から聞きました。使い方についてはこちらの記事が参考になります。

git-new-workdir が便利 - #生存戦略 、それは - subtech
http://subtech.g.hatena.ne.jp/secondlife/20121207/1354854068

(おまけ) あるビューがどのクラスのものかを特定する

こちらはGitには関係が無いのですがコードリーディングに関連するのでおまけとして記載しておきます。

既存のプロダクトをさわる際、自分が手を加えたいビューに関するコードがどのファイルに書かれているものかを探るのは意外に大変です。コードを読もうにもどこに書いてあるかがわからずファイルの探索に時間を取られてしまう場合があります。RailsアプリとiOS/Androidアプリのそれぞれで、実行時に表示されているビューからそれを司るファイルを特定する方法をご紹介します。

Railsアプリ

クックパッドではr7kamura/view_source_mapというgemを利用しています。これを利用するとレンダリングされたHTMLソース内に、どこがどのpartialのものかをコメントとして表示することができます。

r7kamura/view_source_map
https://github.com/r7kamura/view_source_map

iOS/Androidアプリ

Xcodeではアプリを実行中に"Debug View Hierarchy"ボタンをクリックすると次の画像のようにビューの階層構造が可視化されます。この画面で任意のビューをクリックするとそのビューのクラス名が表示されます。

Android Studioにも類似の機能があり、アプリの実行中にAndroid Device Monitorから"Dump View Hierarchy for UI Automator"ボタンをクリックすると実行画面が表示され、ビューをクリックすると右側のリストからそのクラス名が確認できます。

まとめ

この記事では既存のコードを読解するための工夫についてお話しました。皆さまの日々のコードリーディングの一助になれば幸いです。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/