Skip to content
Published on

他人が書いたコードの読み方

Authors

はじめに — 私たちは書くより読む

新しいチームに合流した最初の週を思い浮かべてください。50万行のコードベース、初めて見るフレームワーク、Wikiは2年前で止まっており、そのコードを書いた人はすでに退職しています。あなたの仕事は、この中でバグを一つ直すこと。どこから始めればいいでしょうか。

プログラミングを「コードを書く仕事」と考えがちですが、実際に開発者が時間を使う割合は正反対です。よく引用される目安として、読むと書くの比率はおよそ10対1と言われます。機能を一つ追加するにはその周りの既存コードを読んで理解する必要があり、バグを直すには原因のコードを読み、レビューをするには他人のコードを読みます。私たちは書く人というより、読む人なのです。

ところが不思議なことに、コード読解は誰も体系的に教えてくれません。書き方は講義も本もあふれているのに、読み方は「やっているうちに上達する」と放置されます。この記事は、見知らぬコードベースに向き合ったときに使える具体的な戦略をまとめます。一行に圧縮すればこうです。すべての行を読もうとするな。データが流れる道を追え。

エントリポイントを見つけよ

コードを読むとき、まず最初にすべきは**エントリポイント(entry point)**を見つけることです。プログラムが実際に実行を開始する地点のことです。適当なファイルを開いて上から読むのは、地図もなく森の真ん中に落とされるようなものです。

エントリポイントはプログラムの種類によって異なります。

  • CLIツールmain()関数、あるいはpackage.jsonbinpyproject.tomlのスクリプト定義。
  • Webサーバー:ルーティングテーブル。「このURLにリクエストが来たらこのハンドラが処理する」というマッピングが地図の役割を果たす。特定の機能を理解したいなら、その機能のAPIパスから探して該当ハンドラに入る。
  • フロントエンドアプリ:ルートコンポーネント(例:App)とルーター設定。特定の画面が気になるなら、そのルートに紐づいたコンポーネントから。
  • ライブラリ:公開API。indexファイルやパッケージマニフェストのexportsが「外から使う入口」だ。ここから内側へ掘る。

エントリポイントを見つける実用的なコツ:エラーメッセージ、ログ文言、UIに見えるテキストをgrepせよ。 画面に「決済失敗」という文言が出るなら、その文字列をコードで検索してみてください。たいてい目的の機能のまさにその地点へワープします。これは本当に強力なのに、忘れられがちな技法です。

すべての行ではなくデータを追え

見知らぬコードを最初から最後まで一行も漏らさず読もうとする試みは、ほぼ必ず失敗します。何日費やしても頭の中に絵が描けません。理由は単純です。コードの大部分は、今あなたが解こうとしている問いと無関係だからです。

はるかに良い戦略は**データを追うこと(follow the data)**です。特定の値を一つ決め、それがどこで生まれ(入力・生成)、どんな変換を経て(加工)、どこへ行くか(保存・出力)、その流れだけを追います。残りのコードはひとまず無視します。

たとえば「アップロードした画像がなぜ回転して保存されるのか?」を調査するなら:

  1. 画像が入ってくる地点を見つける(アップロードハンドラ)。
  2. その画像データがどの変数に入るか見て、その変数を追う。どの関数に渡されるか?
  3. 渡された関数の中で画像がどう変形されるか(リサイズ、フォーマット変換、EXIF処理……)。
  4. 最終的にどこに保存されるか。

この連鎖だけ追えば、50万行のうち実際に読むのは数十行です。回転の問題なら、3番のステップのEXIF処理の近くで犯人が出るでしょう。無関係な認証ロジック、決済ロジック、管理画面は一行も読まなくて構いません。

データを追うときに役立つ思考の道具が**データの形(shape)**です。各ステップで「今この値は何の型で、どんな構造か?」を問い続けてください。ここではファイルパス文字列、ここではバイトバッファ、ここではデコードされたピクセル配列……形の変化を追跡すると流れが鮮明になります。

テストと型をドキュメントとして使え

公式ドキュメントはたいてい古いか、存在しません。しかし嘘をつかない二種類のドキュメントが、すでにコードの中にあります。テストと型です。

テスト = 実行可能な仕様

ある関数が何をするか知りたいなら、その関数のテストをまず読んでください。 テストは「この入力を与えるとこの出力が出る」を具体例で示す、実行可能な取扱説明書です。しかもCIが通っているなら、その説明は今この瞬間です。古いWikiと違い、テストはコードとともに検証されます。

テストで特に価値があるもの:

  • エッジケースdescribe/itブロックの名前が、その関数が気にかける境界を列挙してくれます。「配列が空のとき」「負の数のとき」「同時リクエストのとき」——開発者が何を心配したかが露わになります。
  • 使用例:テストの準備(arrange)部分は、その関数を実際にどう呼ぶかを示します。APIの使い方をドキュメントの代わりにここで学びます。

機能を理解したくてテストがあるなら、一つ選んでデバッガでステップを踏んでみてください。実行されるコードを追いながら読むほど速い理解はありません。

型 = 契約

静的型のあるコード(TypeScript、Rust、Go、型ヒント付きPythonなど)なら、型シグネチャがそのまま契約書です。関数の本体を読む前に、シグネチャだけで半分は理解できます。何を受け取り(入力型)、何を返すか(戻り型)が明示されているからです。

型はデータの形をコードで固定したものでもあります。先の「データを追う」をやるとき、各ステップの型をIDEで確認すれば、形の変化がそのまま見えます。型のない言語でも、関数冒頭の検証ロジックやドキュメントコメントが似た役割を果たします。

コールグラフを描け — grepとIDEジャンプ

コードを読んでいると、絶えず二つの問いに出くわします。「この関数はどこで定義された?」と「この関数は誰が呼ぶ?」。この二つに素早く答える能力が、コード読解の速度を左右します。

  • 定義へジャンプ(go to definition):あるシンボルがどこで定義されたかへワープ。IDEのこの機能はコード読解の基本技です。上から下へ(呼ぶ側 → 呼ばれる側)掘るときに使います。
  • 参照を探す/呼び出し元を探す(find references / find callers):逆に、ある関数がどこで使われているかをすべて探す。下から上へ(定義 → 使用箇所)遡るときに使います。「この関数を直すと何が影響を受ける?」を問うとき必須。

IDEがない、複数言語が混ざってIDEが追えない、サーバー上でコードだけ開いている——そんなときは**grep(またはripgrep、rg)**が万能の鍵です。

# この関数がどこで呼ばれるかをすべて探す
rg "processPayment\("

# この文字列(エラーメッセージ、ラベル)がコードのどこにあるか
rg "決済失敗"

# この環境変数を誰が読むか
rg "STRIPE_SECRET_KEY"

# 特定の拡張子だけ、行番号付きで
rg -t ts "useAuth" -n

頭の中でこれらのジャンプを繰り返すと、コールグラフが描けます。「AがBを呼び、BがCとDを呼び、Cはあちこちから呼ばれる」。複雑な流れは紙やホワイトボードにこのグラフを実際に描いてみてください。ノード五つ六つ描くだけで、頭がずっとすっきりします。

こうしたgrep・ジャンプのワークフローをgit履歴の探索とともに手に馴染ませたいなら、Git実習場でブランチを行き来しながらコードがどう変わってきたかを追う練習ができます。git log -S"関数名"で「このコードがいつ追加されたか」を探すのも、コード読解の強力な武器です。

実際に動かしてprintを仕込め

静的に読むだけでは解けない地点が必ず来ます。コードが「何をできるか」は読めば分かりますが、「実際に今回は何をしたか」は動かさないと分かりません。条件分岐が複雑だったり、値が実行時に決まったり、コールバックやイベントで流れが飛んだりすると特にそうです。

だからまず動かしてください。 そして気になる地点にprint(またはログ)を仕込んで、実際の値を自分の目で確認してください。

  • 「ここに本当に到達するか?」:到達が疑わしいコードにprint("HERE reached")を仕込む。出なければその分岐は通っておらず、あなたの流れの理解が間違っている。
  • 「この値はここで何だ?」:変数の値をラベル付きで出力し、形と中身を確認する。頭の中の推測と実際が違うことが驚くほど多い。
  • 呼び出し順序:複数の関数に入口ログを仕込めば、実際の実行順序が露わになる。非同期コードで特に有用。

これは先に扱った「データを追う」の動的版です。静的に流れを追って詰まったら、その地点にprintを打ち込んで実際のデータを捕まえます。読むことと動かすことを交互にするのが、見知らぬコードを理解する最速の道です。

何かを変えて何が壊れるかを見よ

コードを理解する、驚くほど効果的なのに過小評価された方法があります。わざと何かを変えてみて、何が壊れるかを観察することです。

ある関数、ある設定、ある定数が何の役割を果たすか確信が持てないなら、それを変えてみてください。値を反転し、行をコメントアウトし、定数を変な値にしてから動かします。すると、システムが反応で答えをくれます。

  • ある行を消しても何も変わらない → その行は(少なくともこの経路では)重要でない。デッドコードかもしれない。
  • 定数を変えたら特定の画面が壊れた → その定数はその画面と繋がっている。繋がりを一つ突き止めた。
  • 関数の戻り値をハードコードしたらテストが三つ失敗した → その三つのテストがこの関数に依存している。影響範囲が露わになる。

これは能動的な実験です。コードを受動的に眺める代わりに、システムに問いを投げて答えを受け取るのです。もちろん安全にやってください。本番ではなくローカルで、gitでいつでも戻せる状態で。git stashやブランチを使えば、思う存分壊してから元に戻すのがタダです。「理解できなければ壊して戻す」はコード読解の強力なループです。

上から下へ読み、それから掘れ

見知らぬコードベースを理解する順序は、おおむねトップダウンが正解です。細部から掘ると木ばかり見て森を見失います。大きな絵を先に掴み、必要な部分だけ選んで深く入ってください。

おすすめの順序はこうです。

  1. ディレクトリ構造を眺める。 フォルダ名だけで関心の分離が見える。api/db/components/services/……どこに何が住んでいるか地図を作る。
  2. README、設定ファイル、マニフェストを読む。 package.json/pyproject.tomlの依存リストは「このプロジェクトが何のツールで何をするか」を圧縮して示す。スクリプト定義は「これをどうビルド・実行・テストするか」を教える。
  3. アーキテクチャの層を把握する。 リクエストが入ってから出るまで、どの層を通るか(ルーター → コントローラ → サービス → リポジトリのような)。この骨格を掴めば、新しいコードに出会っても「ああ、これはサービス層だ」と位置づけられる。
  4. いよいよ特定の機能を一つ選び、データを追って深く掘る。 ここで先に述べたエントリポイント探し、データ追跡、print、grepが総動員される。

核心は広く浅く → 狭く深くの順序です。最初から一つの関数に沈むと、それが全体でどんな位置なのか分からず方向を見失います。地図を先に描き、その上で一本の道を選んで歩き下りてください。

まとめ — 見知らぬバグを30分で

断片を一つの流れに編んでみましょう。最初の週、「決済後の確認メールがときどき届かない」というバグを受け取ったとします。

  1. 大きな絵(トップダウン):ディレクトリを眺め、services/email/services/payment/があると確認。メールと決済が分かれているな。
  2. エントリポイント + grep:確認メールに入っていそうな文言(「ご注文が完了しました」)をgrep。メールテンプレートとそれを送る関数sendOrderConfirmationを見つける。
  3. 呼び出し元を探すsendOrderConfirmationがどこで呼ばれるか参照を探す。決済成功ハンドラで呼ばれている。
  4. データを追う:決済成功 → メール送信へと続く連鎖を追う。途中に「メール送信をキューに入れる」ステップが見える。
  5. テストを読む:この送信ロジックのテストを読むと、「キューが満杯なら黙って捨てる」ケースがある。臭う。
  6. 変えてみる + print:キュー投入地点にログを仕込んで再現を試みる。トラフィックが集中したときキューが溢れ、一部のメールが捨てられるのを目で確認。

30分前まで50万行の未知だったコードから、関係する数十行だけ読んで原因を突き止めました。一行も漏らさず読もうとしていたら、初日が丸ごと終わっていたでしょう。

おわりに

コード読解は才能ではなく技術であり、技術だから方法があります。核心原則を再びまとめると:

  • 適当に開かず、まずエントリポイントから探す。
  • すべての行ではなくデータが流れる道だけを追う。
  • テストと型を嘘をつかないドキュメントとして使う。
  • grepとIDEジャンプでコールグラフを描く。
  • 静的に詰まったら動かしてprintを仕込む。
  • 確信が持てなければ変えてみて何が壊れるかを見る。
  • 上から下へ大きな絵を先に、それから深く。

次に見知らぬコードベースの前で途方に暮れたら、最初のファイルを開いて上から読もうとする衝動をこらえてください。代わりに問うのです。「エントリポイントはどこだ? 私が追うデータは何だ?」この二つの問いが、未知の森に切り開いた最初の道です。

参考資料