「リーダブルコード」要約_10章

# 10章 無関係の下位問題を抽出する

10章でのアドバイスは、「無関係の下位問題を積極的に見つけて抽出すること」だ。
そのためには、以下の3点を実践すれば良い。

1. 関数やコードブロックを見て「このコードの高レベルの目標は何か？」を自問する。
2. コードの各行に対して「高レベルの目標に直接効果はあるか」それとも
「無関係の下位問題を解決しているか」を判断する。
3. 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする。

以下、具体的な例で解説する。

**入門的な例**
以下のようなJavaScriptのコードがあるとする。
このコードの高レベルの目標は「与えられた地点から最も近い地点を見つける」ことである。

```javascript
// 与えらた軽度・緯度に最も近い'array'の要素を返す。
// 地球が完全な球体であることを前提としている。
var findCloseLocation = (lat, lng, array) => {
  var closest;
  var closest_dist = Number.MAX_VALUE;
  for (var i = 0; i < array.length; i += 1) {
    // 2つの地点をラジアンに変換する。
    var lat_rad = radians(lat);
    var lng_rad = radians(lng);
    var lat2_rad = radians(array[i].latitude);
    var lng2_rad = radians(array[i].longitude);

    // 「球面三角法の第二余弦定理」の公式を使う。
    var dist = Math.acos(
      Math.sin(lat_rad) * Math.sin(lat2_rad) +
      Math.cos(lat_rad) * Math.cos(lat2_rad) *
      Math.cos(lng2_rad - lng_rad));
    if (dist < closest_dist) {
      closest = array[i];
      closest_dist = dist;
    }
  }
  return closet;
};
```

ループ内のコードは、「２つの地点の球面距離を算出する」という下位問題を扱っている。
これを別の関数`spherical_distance()`として抽出すると良い。
そうすると、全体を以下のように書き直すことができる。
```JavaScript
// 2つの地点(緯度・経度)の球面距離を算出する。
const spherical_distance = (lat1, lng1, lat2, lng2) => {
  //2つの地点をラジアンに変換する。
  var lat1_rad = radians(lat1);
  var lng1_rad = radians(lng1);
  var lat2_rad = radians(lat2);
  var lng2_rad = radians(lng2);

  // 「球面三角法の第二余弦定理」の公式を使う。
  return Math.acos(Math.sin(lat11_rad) * Math.sin(lat2_rad) +
         Math.cos(lat1_rad) * Math.cos(lat2_rad) *
         Math.cos(lng2_rad - lng1_rad));
};

const findClosestLocation = (lat, lng, arry) => {
  var closest;
  var closest_dist = Number.MAX_VALUE;
  for (var i = 0; i < array.length; i += 1) {
    var dist = spherical_distance(lat, lng,  array[i].latitude, array[i].longitude);
    if (dist < closest_dist) {
      closest = array[i];
      closest_dist = dist;
    }
  }
  return closest;
};
```
コードが読みやすくなり、高レベルの目標に集中できるようになった。
さらに、`spherical_distance()`は個別にテストができる関数であり、将来的に再利用も可能だ。

純粋なユーティリティコード
例えば、以下のような基本的なタスクの実装については、プログラミング言語側で用意されている「組み込み関数」が利用できる場合が多い。
* 文字列の操作
* ハッシュテーブルの使用
* ファイルの読み書き

具体的には、ファイルの中身を全て読み込みたければ、PHPなら`file_get_contents("filename")`が使える、といったように。

しかし、こうした基本的なタスクであっても、それ用の組み込み関数が用意されていない場合もある。
そういったケースでは、自分で関数を書くことが望ましい。
そうして作ったコードは、複数のプロジェクトで使えるユーティリティ（便利な）コードになっていくだろう。


**その他の汎用コード**
以下のJavaScriptのコードでは、Ajaxでデータをサーバに送信し、デバッグのために返ってきた値を表示している。
```JavaScript
ajax_post({
 url: 'http://XXX.com/submit',
 data: data,
 on_success: function (response_data) {
   vat str = "{\n";
   for (var key in response_data) {
       str += " " + key + "=" + response_data[key] + "\n";
   }
   alert(str + "}");
   //引き続き'response_data'の処理
 }
});
```

このコードの高レベルの目標は「サーバーをAjaxで呼び出してレスポンスを処理する」である。
しかし、コードの大部分は「レスポンスを見やすく整形して出力する」という「無関係の下位問題」を扱っている。
この整形処理は、format_pretty(obj)という別の関数として抽出できる。
```javascript
var format_pretty = function (obj) {
 var str = "{\n";
 for (var key in obj) {
   str += " " + key + "=" + obj[key] + "\n";
 }
 return str + "}";
};
```

それにより、以下のようなメリットがある。
* `format_pretty()`を抽出したことで、元のコードが簡潔になった。
* `format_pretty()`は独立した関数のため、改善が楽で、かつ再利用も可能。

**汎用コードをたくさん作る**
汎用コードの素晴らしい点は、プロジェクトから完全に切り離されている点だ。
このようなコードは、開発もテストも理解も楽である。
かつ、複数のプロジェクトで再利用できるので、簡単に共有できるよう特別なディレクトリ(例:util/）を用意するのが良い。

**プロジェクトに特化した機能**
抽出する下位問題というのは、プロジェクトから完全に独立したものである方が良い。
ただし、完全に独立していないくても、それはそれで問題ない。
下位問題を取り除くだけでも効果がある。

**10章のまとめ**
* プロジェクト固有のコードから汎用コードを分離する
* ただし、やりすぎは良くない。小さい関数を作りすぎると逆に読みづらくなるため。
やみくもに分離させるのではなく、再利用できる場合に分離させることが望ましい。