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

第6章 ほかの人のコードを読もう―無理なく始められるコードリーディングのコツ

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

コードの書きかたやコミットのしかたが大事なのはわかっても,自分で実践するのはなかなか難しいですね……

そういうときは,ほかの人のコードやコミットを見るといいよ。自分にない視点,これまで知らなかった書きかた,テクニックなど,先輩たちの良い行いを手本にすれば,良い習慣を効率良くものにできるね

でも,社内のリポジトリにはすでに大量のコードがあるし,どこから読めばいいか見当も付かないです

そうだなあ,じゃあほかの人のコードを多く読むためのコツもいくつか教えておこうか

すべてのコードを読む

最も単純なのは,小説を読むようにすべてのコードを頭から読むというやりかたです。

この方法では,リポジトリを手元にgit cloneしたり,ファイルをダウンロードしたりして,ファイルの先頭から順番に読んでいきます。GitHub上のリポジトリやMercurialリポジトリでは「ファイルブラウザ」という機能を使ってWeb上でリポジトリの内容を直接見ることもできます図1)⁠

図1 GitHubのファイルブラウザ

図1 GitHubのファイルブラウザ

この読みかたは,1~数個程度の少ないファイルで完結しているライブラリやユーティリティなど,小規模なソフトウェアの全容を把握したいときに有効です。たとえば,Node.jsでchmodコマンド相当のことを可能にするchmodというnpmライブラリのGitHub上のリポジトリを参照すると,ドキュメント類に混じってindex.jsという名前のJavaScriptのファイルが1つだけ置かれています。ファイルブラウザで内容を確認すると,およそ1KBの中に必要なことがすべて書かれており,このライブラリが1つのファイルだけで完結した実装になっていることが見て取れます。この程度であれば,全体を読み下して設計から詳細な仕様までを把握するのも容易でしょう。

しかしその裏返しとして,この読みかたは,業務で書かれたソフトウェアや大規模なOSSのような巨大なソースコードをすべて読むには不向きです。対象のことを何も知らない状態から巨大なソースコードを読み始めるのは気が重いものですし,そもそも,どのファイルから読めばよいのかすらわからないことのほうが多いでしょう。

興味のある個所だけを読む

もう1つの読みかたは,辞書やリファレンスマニュアルを引くように必要な個所から読むというやりかたです。

ある程度以上の規模のソフトウェアになると,すべてのコードを頭から読むのは現実的ではありません。そのような場合は,興味のある個所の実装だけを読むようにすれば,前項の方法よりは負担が小さく済みます。バグ修正や機能追加を行うときは,筆者はたいていこの読みかたをします。

「興味のある個所」をソースコードの中から特定する方法はさまざまです。いくつか代表的なアプローチを紹介しましょう。

典型的なファイル配置を手がかりにする

ソフトウェアの種類によっては,典型的なファイル配置が決まっていることがあります。たとえばRubyのGemパッケージやNode.jsのnpmパッケージになっているソフトウェアは,重要な実装はlib以下に,付属のコマンドラインツールはbin以下に,自動テストはtest以下に,といった要領でファイルが配置されています図2)⁠あるパッケージで導入されるコマンドラインツールの実装を読みたければbin内を見てみる,という要領でファイルのありかの見当を付けられます。

図2 Gemパッケージのディレクトリ構成

図2 Gemパッケージのディレクトリ構成

たとえばRuby製のプレゼンツールRabbitはスライドのひな型を自動生成するrabbit-slideというコマンドラインツールを提供していますが,一般的なファイル配置にのっとると,その実体はbin/rabbit-slideの位置にあるファイルであることがわかります。そこでbin/rabbit-slideの内容を見てみると,リスト1のように,ライブラリをロードしているだけであることが読み取れます注1)⁠

リスト1 見つかったrabbit-slideコマンドの実装の内容

#!/usr/bin/env ruby
(中略)

require "rabbit/command/rabbit-slide"

exit(Rabbit::Command::RabbitSlide.run(*ARGV))

ライブラリのパスがrabbit/command/rabbit-slideと指定されていますが,これはRubyのGemパッケージに含まれるライブラリのパスの一般的な指定方法です。rabbit/command/rabbit-slideは,その環境にインストールされているいずれかのGemパッケージのlib/以下に含まれているRubyスクリプトファイルのパスを示しており,Rabbit自身のlib/rabbit/command/rabbit-slide.rbがその実体です。

このように,ファイルの位置を特定してその内容を見て……という手順を繰り返していけば,探索範囲を広げて実装の詳細をたどることができます。

注1)
引用個所のソースコードはhttps://github.com/rabbitshocker/rabbitから入手できます。

ソースコード内をキーワードで検索する

読みたい実装のありかがまったくわからない場合は,クラス名やメソッド名,コマンド名などを手がかりに実装を探すという方法もあります。オンラインでソースコードを参照できるプロジェクト注2では,Web上でソースコード内の検索結果を閲覧できる場合があります。そうでない場合は,ソースのスナップショットをダウンロードしたり,リポジトリをcloneしたりしたうえで探索します。

たとえば,Node.js用のnpmライブラリの1つであるencodingconvert()というメソッドは,おそらくfunction convert() {}convert = function() {}のようなJavaScriptのコードで定義されているだろうと推測できますので,function convert\(|convert = functionといった正規表現でメソッドの定義個所を探せます。grepまたはgit grep注3であれば-Eオプションで正規表現での検索ができますし,-nオプションを加えれば見つかった個所の行番号も出力できますので,どのファイルのどの行が正規表現にマッチしたのかをすぐに調べられます図3)⁠

図3 git grepでソースコードを検索する

$ git grep -n -E "function convert\(|convert = function" lib/
lib/encoding.js:26:function convert(str, to, from, useLite) {

lib/encoding.jsの位置にあるファイルの26行目だけがマッチしていますので,そのファイルをテキストエディタやページャで開いて該当行の前後を見れば,機能の詳細がわかります。リスト2は,実際のコードの抜粋です注4が,ここから,一般的な使い方の説明には書かれていない詳細な仕様がコメントとして書かれている様子や,エンコーディング名が省略されている場合のフォールバック先のエンコーディング名がUTF-8と決め打ちされている様子などが読み取れます。また,ここから呼び出している別のメソッドの名前でgrepしなおす……という手順を繰り返すことで,探索範囲を広げてより深い実装まで分け入っていくこともできます。

リスト2 見つかったencodingのconvertメソッドの実装個所

/**
  * Convert encoding of an UTF-8 string or a buffer
  *
  * @param {String|Buffer} str String to be converted
  * @param {String} to Encoding to be converted to
  * @param {String} [from='UTF-8'] Encoding to be converted from
  * @param {Boolean} useLite If set to ture, force to use iconvLite
  * @return {Buffer} Encoded string
  */
function convert(str, to, from, useLite) {
    from = checkEncoding(from || 'UTF-8');
    to = checkEncoding(to || 'UTF-8');
    str = str || '';

メソッドの名前以外に,特徴的なメッセージも探索の手がかりになります。たとえばブラウザのFirefoxを使っていて「Failed to read the configuration file.Please contact your system administrator.」注5というメッセージが表示される場面の詳細を調べたいときは,まずメッセージを手がかりにソースコード内を検索して注6)⁠その定義個所を見つけます。この例では,メッセージの全文で検索するとソース中の1ヵ所だけがヒットしました図4)⁠

図4 メッセージを定義している個所が見つかった

図4 メッセージを定義している個所が見つかった

Firefoxのように国際化が考慮されているソフトウェアでは,画面上に表示されるメッセージには言語によらない識別子が割り振られていることが多いです。この例でも,メッセージに対してreadConfigMsgという内部的な識別子が割り当てられていることが見て取れます。そこで,今度はこの識別子でソースコードを検索しなおします。すると今度は,ソース中の2ヵ所がヒットしました図5)⁠

図5 メッセージの識別子を参照している個所が見つかった

図5 メッセージの識別子を参照している個所が見つかった

見つかった2ヵ所のうち2つ目は先ほど見たメッセージの定義個所で,1つ目がメッセージを参照している個所です。1つ目の個所の前後のソースを見れば,メッセージを表示するためにどんな処理が行われているのかがわかります。また,その個所が含まれている メソッドの名前で再度検索すれば,どんな文脈や条件でそのメッセージが表示されるのかもわかります。

注2)
GitHub などでリポジトリを公開しているプロジェクトでは,最新のソースコードをWeb上で閲覧できる場合が多いです。
注3)
通常のgrepコマンドだとキャッシュなどのファイルまで検索対象になってしまいますが,git grepではリポジトリでバージョン管理されているファイルだけが検索対象になります。
注4)
引用個所のソースコードはhttps://github.com/andris9/encodingから入手できます。
注5)
日本語版では「設定ファイルを正常に読み込めませんでした。システム管理者に問い合わせてください。」
注6)
Firefoxのソースコードの場合,Mozillaが提供しているオンラインのソースコード検索システムMXRを使うと楽です。

発生している障害を手がかりにする

あるソフトウェアを利用していて障害が発生した場合,障害の発生個所を調べると原因や回避方法がわかるかもしれません。これも「興味のある個所」と言えるでしょう。

未知の例外が発生してソフトウェアの動作が止まってしまったときに,スタックトレース注7が出力される場合があります。スタックトレースはエラーが発生した個所からメソッド(関数)の呼び出し元をたどっていったもので,バグを修正するための重要な情報です。

図6は,先にも登場したRabbitを使用しているときに出力されたスタックトレースの例です。各行は「例外が発生したファイルのパス」⁠行番号」⁠メソッドの名前」が出力されています。実際に例外がraiseされたのは1行目の個所で,2行目以降はその呼び出し元が順番に出力されています。また,1行目にはメソッド名に続けて,例外のクラス名や補足情報が出力されています。

図6 スタックトレースの例

vendor/bundle/ruby/2.2.0/gems/glib2-2.2.4/lib/glib2/deprecatable.rb:85:in `signal_connect': no such signal: expose-event
(GLib::NoSignalError)
        from vendor/bundle/ruby/2.2.0/gems/glib2-2.2.4/lib/glib2/deprecatable.rb:85:in `block (3 levels) in extended'
        from lib/rabbit/renderer/display/drawing-area-primitive.rb:139:in `set_expose_event'
        from lib/rabbit/renderer/display/drawing-area-primitive.rb:119:in `init_drawing_area'
        (中略)
        from lib/rabbit/command/rabbit.rb:51:in `run'
        from lib/rabbit/command/rabbit.rb:29:in `run'
        from bin/rabbit:22:in `<main>'

各行のファイルパスを見ると,1行目と2行目はbundlerで導入された依存ライブラリのパスであることが見て取れます。Rabbit固有のファイルのパスは3行目以降に現れていますので,Rabbitをデバッグしている場面であれば,3行目以降だけを見ればよいと言えます。あとは,例外が発生した個所の周辺のコードだけを読んでもよいですし,より広い範囲の実装の全容を把握するための出発点にしてもよいでしょう。

注7)
バックトレースとも言います。

コメント

コメントの記入