「リーダブルコード」を改めて読んでみて(その4)

「リーダブルコード」に関する記事の続きです。さくっと読み切れると思っていたのですが、何だかんだで1ヶ月以上かかりそうです…

今回の記事では第Ⅲ部の中の10章の内容に触れていきます。
本当は部単位で1つの記事にまとめようと思っていたのですが、10章はコードを実際に見て理解することが重要な内容であり、またコードの量も少し多めだったので、今回は分割して書くことにしました。

第Ⅲ部は「コードの再構成」というテーマで、10章では以下の点にフォーカスしていました。

いよいよコードのリファクタリングの話へと進んでいきます。


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

10章では、無関係の下位問題を抽出する方法について言及しています。
具体的なコードを見る前に、「無関係の下位問題を抽出する」とはどういうことか、またそのメリット・デメリットをまとめておきます。

  • 「無関係の下位問題を抽出する」とは
    • 関数やコードブロックの目的に対して直接関係のない処理別の関数として再宣言すること

  • メリット
    • 再利用できる=プロジェクト内の別の処理や別プロジェクトで利用できる
    • 単体でのテストができる=不具合の原因が特定しやすくなる
    • 抽出元の関数やコードブロックの固有かつ重要な部分に意識を集中できる
  • デメリット
    • やりすぎると読みづらくなる=コードを読むときに関数を追うコストが増える


ユーティリティクラスやヘルパークラスなどと呼ばれる汎用クラス、ライブラリなどは無関係の下位問題を抽出した典型的な例です。
なお、汎用クラスなどの是非についてここで議論するつもりは一切ありませんので、あらかじめご了承ください。

ここからは具体例を2つ取り上げ、無関係の下位問題を抽出する方法を見ていきます。

例1

ここまでずっとサンプルコードをKotlinに書き直していたのですが、あまり意味がないことに気づいたので笑、JavaScriptのまま記載します。
以下の関数の目的は「与えられた地点から最も近い場所を見つける」ことです。

// 与えられた緯度経度に最も近い'array'の要素を返す。
// 地球が完全な球体であることを前提としている。
var find_closest_location = function (lat, lng, array) {
  var closest;
  var closest_dist = Number.MAX_VALUE;
  for (var i = 0; i < array.length; i++) {
    // 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 closest;
};


ここで、コメントの書かれた2ヶ所は、この関数の目的である「与えられた地点から最も近い場所を見つける」ことには直接関係のない処理です。

  • 2つの地点をラジアンに変換する。
  • 「球面三角法の第二余弦定理」の公式を使う。


球面距離を算出する具体的な方法に関してはこの関数の関心事ではないので、これらの処理を抽出します。

具体的には以下のようになります。

// 2つの地点(緯度経度)から球面距離を算出する。
var spherical_distance = function (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(lat1_rad) * Math.sin(lat2_rad) +
                   Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng1_rad));
};

// 与えられた緯度経度に最も近い'array'の要素を返す。
// 地球が完全な球体であることを前提としている。
var find_closest_location = function (lat, lng, array) {
  var closest;
  var closest_dist = Number.MAX_VALUE;
  for (var i = 0; i < array.length; i++) {
    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を呼ぶだけで値を取得できるようになります。

例2

次は、JavaScriptCookie操作を行う処理を取り上げます。
使ったことのある方なら分かると思うのですが、JavaScriptCookie周りのインタフェースは本当にイケてないんですよね。

JavaScriptでは、document.cookieというプロパティを介してCookieを操作します。
このプロパティには各パラメータがkey1=value1; key2=value2; ...という形式で保存されています。

読み取り・書き込み・削除のいずれも正直扱いづらいのですが、今回はCookieからmax_resultsというキーに対応する値を取得するケースを考えます。

具体的には以下のようになります。

var max_results;
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
  var c = cookies[i];
  c = c.replace('/^[ ]+/',''); // 先頭の半角スペースを削除する。
  if (c.indexOf('max_results=') === 0) {
    max_results = Number(c.substring(12, c.length)); // 12は「max_results=」の文字数。
  }
}


これだけでも扱いづらさを十分ご理解いただけたかと思います。
Cookieを操作する具体的な方法に関してはCookieから値を取得するときの関心事ではないので、これらの処理をget_cookie()として抽出します。

具体的には以下のようになります。

function get_cookie(key) {
  var cookies = document.cookie.split(';');
  for (var i = 0; i < cookies.length; i++) {
    var c = cookies[i];
    c = c.replace('/^[ ]+/',''); // 先頭の半角スペースを削除する。
    var searchValue = key + '=';
    if (c.indexOf(searchValue) === 0) {
      max_results = Number(c.substring(searchValue.length, c.length));
    }
  }
}

var max_results = Number(get_cookie('max_results'));


このようにすることで、Cookieを操作する方法の詳細を意識することなく、get_cookie()を呼ぶだけで値を取得できるようになります。


一連の記事はこちらです。
tkhs0604.hatenablog.com tkhs0604.hatenablog.com tkhs0604.hatenablog.com tkhs0604.hatenablog.com tkhs0604.hatenablog.com tkhs0604.hatenablog.com