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

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

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

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

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

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

すべてのコードを読む

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

この方法では、リポジトリを手元に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がその実体です。

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

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

読みたい実装のありかがまったくわからない場合は、クラス名やメソッド名、コマンド名などを手がかりに実装を探すという方法もあります。オンラインでソースコードを参照できるプロジェクト[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つ目の個所の前後のソースを見れば、メッセージを表示するためにどんな処理が行われているのかがわかります。また、その個所が含まれている メソッドの名前で再度検索すれば、どんな文脈や条件でそのメッセージが表示されるのかもわかります。

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

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

未知の例外が発生してソフトウェアの動作が止まってしまったときに、スタックトレース[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行目以降だけを見ればよいと言えます。あとは、例外が発生した個所の周辺のコードだけを読んでもよいですし、より広い範囲の実装の全容を把握するための出発点にしてもよいでしょう。

日々のコミットを読む

コードの読みかたとして最後に挙げるのは、テレビやラジオを付けっぱなしにしておくように日々流れてくるコミットを読むやりかたです。

日々のコミットを読む方法はいくつかありますが、クリアコードではコミットメールを読むという方法を最もよく利用しています[8]⁠。

コミットメールの内容

コミットメールとは、リポジトリにコミットされたことを契機に送信される、そのコミットに関する情報を含めたメールのことです。具体的には次のような内容が含まれます。

  • コミット日時
  • コミットした人の情報(名前、メールアドレスなど)
  • コミットメッセージ
  • ソースコードの差分

ここでは、これらすべてを含むメールをコミットメールと呼びます図7注9⁠。

内容を理解すること以外の目的でとにかく「コードを読む」ということを最優先に考えると、この方法であれば比較的低コストでさまざまなコードに触れることができます図8⁠。

図7 コミットメール
図7 コミットメール
図8 流れてくるコミットを見ることを通じて、多くのコードに触れられる
図8 流れてくるコミットを見ることを通じて、多くのコードに触れられる

ただし、間違い探しのためのコードレビューのようにすべてのコミットを1行ずつ丹念に読んでいると、時間がいくらあっても足りません。そのため、この方法では次のような読みかたを推奨します。

  • まず、コミットメッセージを読む
  • 次に、コードを軽く眺める
  • 気になる個所があれば、詳しく読む
  • なければ、次のコミットメールを読む

クリアコードでは、GitHub上の自分のリポジトリに行われたpushの結果を受け取るgithub-web-hooks-receiver他の人のリポジトリに行われたpushの結果を受け取るgithub-event-watcherコミットメールを流すためのgit-commit-mailerといったツール群を公開しています。詳しいセットアップ手順はリポジトリ内の説明を参照してください。

日々のコミットを起点にした読みかた

コミットメールのようなしくみを使って日々のコミットを流し見する方法は、現実的に無理なく続けやすいです。コミットメッセージの書きかたや、コードの書きかたの工夫など、プログラムの内容によらない一般的な作法を学ぶにはうってつけと言えます。

その一方で、個々のコミットには現れない、設計やアルゴリズムの話のような情報を読み取りにくいという欠点があります。また、まったく関心のないソフトウェアやまったく知らないプログラミング言語のコミットは、見ていても頭に入ってきにくいでしょう。

ですので、まずは自分が使っているソフトウェアや読み書きできる言語を採用しているプロジェクトのコミットから読み始めるのがお勧めです。興味のあるキーワードが登場していれば、それを手掛かりにソースコードを探索できますし、ひいては設計上の工夫も学べます。

最後に、本章で紹介した3通りのコードの読みかたのそれぞれについて、メリット/デメリットと適した場面を表1にまとめました。目的に即した読みかたを選ぶ際の参考にしてください。

表1 コードの読みかたとそれぞれのメリット/デメリット
読みかたメリットデメリット適した場面
すべてのコードを読む
  • 対象ソフトウェアの全体を理解できる
  • どこから読めばよいのかわかりづらい
  • 心が折れやすい
  • オフラインで読む場合、ソースコード全体を用意する必要があり、リモートリポジトリの更新に追随するのも面倒
小規模なソフトウェアの全容の把握
興味のある個所だけを読む
  • 興味のある個所の概要や詳細を理解できる
  • より短い時間で読める
  • 対象のソフトウェアの全容は理解できない
  • 初級者にとっては、興味のある個所を見つけるのが難しい
  • オフラインで読む場合、ソースコード全体を用意する必要があり、リモートリポジトリの更新に追随するのも面倒
バグ修正や機能追加
日々のコミットを読む
  • 最初にソースコード全体を用意しなくてもよい
  • コミットメールで読んでいる場合、日々のコミットは自動的に流れてくる
  • 日々流れてくるので、その時点から読み始められる
  • 日々流れてくるので、流れてくる限り読み続けられる
  • 最新の変化にいち早く気がつける
  • 開発が活発でコミット数の多いソフトウェアだと読むのが大変
  • 対象のソフトウェアの全容は理解できない
たくさんのコードに触れる


文書化されたベストプラクティスを学ぶのも大事だけど、それだけじゃ不十分だね。ソースコードやコミット、先人たちが呼吸するかのように無意識に実践していることの中には、まだ文書化されていない活きたノウハウがたくさん詰まっているんだ。そういうところから学ぶのも大事だよ

コミットが流れてくるのって、まるで、開発の様子をライブで見ているみたいですね! これはいろんなことが学べそうです

一度だけ読んでそれ以後は読まないんじゃあ、知識はすぐに古びてしまうからね。流れてくるコミットを眺めるようにすれば、継続的にコードを読むのも習慣にしやすいんじゃないかな


おすすめ記事

記事・ニュース一覧