コードリーディングを楽にしてくれる背景知識

コードリーディングがうまくできない理由の一つに「背景知識が足りない」という問題があります。（参考：[いつ、どんなときにコードリーディングをしていて困っているか？](https://qiita.com/YY-EN40P/items/8fa3abc61e9ed26acac4)）ここで言う背景知識とは、 **いま読んでいるコードには書かれていないけれどコードを理解するために必要となる情報** のことです。背景知識が不足していると、コードを読んでいてもどうしても理解できないところが出てきます。そのため、目の前のコード以外のところで情報収集するか、事前に勉強しておく必要があります。

逆に言えば、背景知識が豊富であればコードリーディングは楽になります。今回はそうしたコードリーディングを楽にしてくれる背景知識について紹介します。

## コードリーディングの際にどんな知識が必要になるか？
まず実例を使って説明します。以下のコードを読むにあたってどんな知識が必要となるでしょうか？

```js:node/lib/path.js
  /**
   * @param {...string} args
   * @returns {string}
   */
  join(...args) {
    if (args.length === 0)
      return '.';

    let joined;
    let firstPart;
    for (let i = 0; i < args.length; ++i) {
      const arg = args[i];
      validateString(arg, 'path');
      if (arg.length > 0) {
        if (joined === undefined)
          joined = firstPart = arg;
        else
          joined += `\\${arg}`;
      }
    }

    if (joined === undefined)
      return '.';

    // Make sure that the joined path doesn't start with two slashes, because
    // normalize() will mistake it for a UNC path then.
    //
    // This step is skipped when it is very clear that the user actually
    // intended to point at a UNC path. This is assumed when the first
    // non-empty string arguments starts with exactly two slashes followed by
    // at least one more non-slash character.
    //
    // Note that for normalize() to treat a path as a UNC path it needs to
    // have at least 2 components, so we don't filter for that here.
    // This means that the user can use join to construct UNC paths from
    // a server name and a share name; for example:
    //   path.join('//server', 'share') -> '\\\\server\\share\\')
    let needsReplace = true;
    let slashCount = 0;
    if (isPathSeparator(StringPrototypeCharCodeAt(firstPart, 0))) {
      ++slashCount;
      const firstLen = firstPart.length;
      if (firstLen > 1 &&
          isPathSeparator(StringPrototypeCharCodeAt(firstPart, 1))) {
        ++slashCount;
        if (firstLen > 2) {
          if (isPathSeparator(StringPrototypeCharCodeAt(firstPart, 2)))
            ++slashCount;
          else {
            // We matched a UNC path in the first part
            needsReplace = false;
          }
        }
      }
    }
    if (needsReplace) {
      // Find any more consecutive slashes we need to replace
      while (slashCount < joined.length &&
             isPathSeparator(StringPrototypeCharCodeAt(joined, slashCount))) {
        slashCount++;
      }

      // Replace the slashes if needed
      if (slashCount >= 2)
        joined = `\\${StringPrototypeSlice(joined, slashCount)}`;
    }

    return win32.normalize(joined);
  },
```

上記コードは`Node.js`の`Path`モジュールで定義されている`join`メソッドです。（`join`メソッドの実装の詳細については[【コードリーディング】Node.js Pathモジュールのjoinメソッド成長記録（2009年〜2022年）前編](https://qiita.com/YY-EN40P/items/4c1ef279498d34126802)にて解説しています。）

この`join`メソッドのコードリーディングを行うと、以下のような知識が必要となります。

- 残余引数（可変長引数とも呼ばれる）`...args`
- 現在のディレクトリへのパスが`.`で表記されること
- 繰り返し処理の回数上限として`args.length`のような配列要素の数を使う定番表現
- `Node.js`で定義されているメソッドの処理内容（`validateString`、`normalize`、`isPathSeparator`、`StringPrototypeCharCodeAt`、`StringPrototypeSlice`）
- `let joined`と変数宣言して何も値を代入していないときは`undefined`という値になるという`JavaScript`の基礎的な文法
- プレースホルダー`${}`を使った文字列への変数代入
- UNCパスとは

残余引数やプレースホルダーを使った変数代入のようなJavaScriptの文法としておさえておくべき知識もあれば、UNCパスのような明らかにコードを読んだだけでは理解することが難しい知識も要求されています。`join`メソッドの例では、UNCパスについて知らなければ後半の`\`（バックスラッシュ）に関する処理が何を目的としているのか理解できないでしょう。このように知識の有無によって、コードリーディングによるコードへの理解は大きく影響されます。

## コードリーディングの際に必要となる背景知識
では、コードリーディングの際に必要となる背景知識にはどういったものがあるのでしょうか？分類してみると以下のような10種類の知識に分けられそうです。もちろんこれらはコーディングの際にも必要となります。

- 文法
- コードベース
- フレームワーク・ライブラリ
- プログラミング手法・設計思想
- 開発環境・実行環境
- 機能仕様
- 技術仕様
- 機能仕様の背景にある制約条件および経緯
- 技術仕様の背景にある制約条件および経緯
- コンピュータサイエンス

コードリーディングを行うためには、最低限その言語の文法を知らなければいけません。そういう意味では、 **文法は背景知識ではなく直接的で必須の知識** です。文法を除いた9種類の知識は背景知識と呼ぶべきものだと考えられますので、一つずつ説明していきます。

### コードベース
コードベースに関する情報や知識は、背景知識と呼ぶべきではないと思われるかもしれません。しかし、いま読んでいるコード以外のコードはすべて背景知識であると捉えることができます。実際にはコードベースのうちのごく僅かな断片しか知りませんが、その僅かな断片となる情報もやはり一つの背景知識なのです。

さきほどの`join`メソッドの例で挙げた以下の内容がコードベースに関する背景知識となっています。

- `Node.js`で定義されているメソッドの処理内容（`validateString`、`normalize`、`isPathSeparator`、`StringPrototypeCharCodeAt`、`StringPrototypeSlice`）

コードベースに関する背景知識が多ければ多いほどコードリーディングが楽になることは説明不要でしょう。実際のところ、さきほど紹介した9種類の背景知識のうち、最も必要となる頻度の高い背景知識はコードベースに関する知識のはずです。

### フレームワーク・ライブラリ
フレームワークやライブラリに関する背景知識も、知っておくとコードリーディングが捗るものばかりです。メソッドやクラスに関する情報は、前述のコードベースに関する背景知識に分類されるものですが、フレームワーク・ライブラリに関する背景知識はそれだけではありません。例えばConvention over Configuration[^1]（設定より規約）のような実装上の指針は、フレームワークやライブラリに採用されています。Ruby on Railsで採用されている命名規則を知らなければ、データベースのテーブルを調べる時に勝手にキャメルケースがスネークケースになっていたり、単数形のモデル名が複数形のテーブル名になっている[^2]ことが理解できないかもしれません。こういった背景知識は、公式のドキュメントや解説してあるブログなどから入手可能です。

### プログラミング手法・設計思想
プログラミング手法・設計思想は、オブジェクト指向プログラミング、手続き型プログラミング、関数型プログラミングのような、ソフトウェアを開発していく上での方針や原則を定めた手法や設計思想のことです。（プログラミングパラダイムとも呼ばれます。）背景知識としてこれらの考え方や原則、定番パターンなどを理解していると、コードリーディングでの理解がスムーズになります。

例えば、オブジェクト指向プログラミングの場合、クラス、ポリモーフィズム、継承という特徴的な設計の原則を知り、それらを拡張させたダックタイピングやデザインパターンについての知識があれば、初見で理解しようと苦しまなくて済むでしょう。

### 開発環境・実行環境
OS、ブラウザ、ネットワーク、ハードウェアのような開発環境または実行環境に関する基礎知識やそれらの環境からの要求仕様も背景知識として重要です。これらの知識はソフトウェア開発を続けているうちに自然と身についていることも少なくないので意識していないかもしれませんが、コードリーディングの際に役立っています。最初に挙げた`join`メソッドの例だと、以下の項目が該当します。

- 現在のディレクトリへのパスが`.`で表記されること

このような知識もコードだけから理解することはできません。知っていれば難しくもないですが、知らないと迷宮入り間違いなしです。

### 機能仕様
何のための関数なのか、何を成し遂げたいからこのクラスがあるのか？という理由を知るには、機能仕様を把握するのが一番の近道です。ソフトウェアの目的がわかれば、そこを起点に推測も可能になるのでコードリーディングも進めやすくなります。

### 技術仕様
機能仕様を実装に落とし込むための設計に関する情報が技術仕様です。コード内のコメント、クラスの一覧表やUMLの図、ホワイトボードに手書きされたアーキテクチャ図なども技術仕様の一種です。機能仕様よりも内部実装に近い情報となりますので、コードリーディングに必要な背景知識としては非常に実践的だと感じられるでしょう。技術仕様がまとめられたドキュメントがあれば積極的に活用したいです。

### 機能仕様の背景にある制約条件および経緯
こちらはまさに背景と呼べるような、実装からはかなり隔たりのある情報です。機能仕様を決定するに至った歴史的な経緯やビジネス上の制約条件、コストや時間に関する制約などがこの背景知識にあたります。不可解な実装の理由をたどっていった先に行き当たるような情報であり、「技術的には不可解で無駄に思えるが、こういった背景だから仕方ない」と割り切ることができれば、立ち止まって実りのない時間を過ごしてしまうことを避けられるでしょう。

### 技術仕様の背景にある制約条件および経緯
機能仕様から技術仕様に落とし込む際にも制約条件が存在します。メモリ容量や動作速度への要求仕様、その他ハードウェアに起因する制約条件などです。時として技術的負債とも言われるようなレガシーコードを読み解くためには、こういった制約条件が連なった歴史的経緯をたどる必要があるでしょう。語りべのような人物がいなければ得られない背景知識かもしれません。

### コンピュータサイエンス
背景知識の最後は、コンピュータサイエンスに関する知識です。アルゴリズムやデータ構造のような実装に近い内容のものから、グラフ理論や暗号理論のような、頻出ではないもののおさえておきたい知識に至るまで幅広いです。網羅することは容易ではありませんが、少しずつでも学んでいく必要があります。これまでに紹介した背景知識と違い、 **コンピュータサイエンスに関する知識は陳腐化しにくい** ため、一度勉強しておくと長く役立ってくれます。

## どうやって背景知識を増やしていくか？
背景知識が多ければ多いほどコードへの理解が楽になるのですが、あまりに膨大な範囲に渡るため、完全に網羅することは不可能です。詳しい人や前任者に聞く、ドキュメントを読む、技術書を読んで定期的に勉強を続けるというような手段を活用して、できるところから少しずつ増やしていくしかありません。

背景知識の中にもかんたんに陳腐化してしまうものとそうでないものがあります。陳腐化してしまうものはその都度入れ替えざるを得ないですが、コンピュータサイエンスの基礎のようなものは陳腐化しにくく、一度勉強しておくと必要な時に調べ直して思い出すだけで良いので実は効率が良いです。もちろん抽象的でとっつきにくく難しくもあるのですが、ぜひおすすめしたいです。仲間を見つけて勉強会を開くのも良いかもしれません。

ソフトウェアエンジニアは勉強を続けられないと務まらないという声を時折耳にしますが、背景知識を補完していくこともその1つなのでしょう。

## おわりに
コードを理解するために必要となる情報として、さまざまな種類の背景知識について紹介しました。日々の業務に対して即効性のあるものから、ソフトウェアエンジニアの教養とでも言うべきものまで幅広い領域におよびます。コードリーディングに行き詰まり、文法的には難しくないはずなのに理解しにくいという状況に陥った際は、「何か背景知識が足りていないのでは？」と疑って、目の前のコード以外のところで情報収集していくと良いでしょう。

[^1]: https://ja.wikipedia.org/wiki/%E8%A8%AD%E5%AE%9A%E3%82%88%E3%82%8A%E8%A6%8F%E7%B4%84
[^2]: https://railsguides.jp/active_record_basics.html#%E5%91%BD%E5%90%8D%E3%83%AB%E3%83%BC%E3%83%AB