13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【忙しい人のためのリーダブルコード要約】第Ⅲ部 コードの再構成

Last updated at Posted at 2024-11-16

はじめに

こちらは著書「リーダブルコード」の学習記録です。
忙しい人のためにも、重要そうな部分を抜粋してまとめました。

こちらは本書における第Ⅲ部の要約です。
第I部の要約はこちらから
第II部の要約はこちらから
第Ⅳ部の要約はこちらから

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

エンジニアリングとは、大きな問題を小さな問題に分割して、それぞれの解決策を組み立てることに他ならない。

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

以下の例の高レベルな目標とは、「与えられた地点から最も近い場所を見つける」こと

const findClosestLocation = function(lat, lng, array) {
  for(const i = 0; i < array.length; i += 1) {
    // 2つの地点をラジアンに変換する
    const lat_rad = radians(lat);
    const lng_rad = radians(lng);
    const lat2_rad = radians(array[i].latitude);
    const lng2_rad = radians(array[i].longitude);

    // 「球面三角法の第二余弦定理」の公式を使う
    const 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));
    ...
  }
};

ループ内のコードは、「2つの地点(緯度・経度)の球面距離を算出する」という無関係の下位問題を扱っている。 コード量が多いので、新しい関数に抽出すると良い。

const spherial_distance = function(lat1, lang1, lat2, lng2) {
  const lat1_rad = radians(lat1);
  const lng1_rad = radians(lng1);
  const lat2_rad = radians(lat2);
  const 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));
};

このようにすることで、spherial_distance関数は将来的に再利用可能になる。
よりわかりやすく言い換えると、プロジェクト固有のコードから汎用的なコードを分離するということだ。

11章 一度に1つのことを

オブジェクトから値を抽出する

ユーザーの所在地を読みやすい文字列(「都市、国」)に整形するプログラムを書くとする。

以下のようにlocation_infoディレクショナリに構造化された情報からすべてのフィールドの「都市」と「国」を選んで連結する。
location_info

LocalityName "Santa Monica"
SubAdministrativeAreaName "Los Angeles"
AdministrativeAreaName "California"
CountryName "USA"


"Santa Monica, USA"

このタスクを実装したコードが以下になる。

const place = location_info["LocalityName"]; // 例: "Santa Monica"
if(!place) {
  place = location_info["SubAdministrativeAreaName"]; // 例: "Los Angeles"
}
if(!place) {
  place = location_info["AdministrativeAreaName"]; // 例: "California"
}
if(!place) {
  place = "Middle-of-Nowhere";
}
if(location_info["CountryName"]) {
  place += ", " + location_info["CountryName"]; // 例: "USA"
} else {
  place += ", Planet Earth";
}

return place;

「一度に1つのタスク」を適用する

このコードが一度に複数のタスクを行っていることに気づく人もいるでしょう。

  1. location_infoディレクショナリから値を抽出する
  2. 「都市」の優先順位を調べる。何も見つからなかったら、デフォルトで「Middle-of-Nowhere」にする
  3. 「国」を取得する。なければ「Planet Earth」にする
  4. placeを更新する

これらのタスクを個別に解決するようなコードに書き換える。
最初のタスク(location_infoから値を抽出)はそのままにする。

// location_infoから値を抽出
const town = location_info["LocalityName"]; // 例: "Santa Monica"
const city = location_info["SubAdministrativeAreaName"]; // 例: "Los Angeles"
const state = location_info["AdministrativeAreaName"]; // 例: "CA"
const country = location_info["CountryName"]; // 例: "USA"

次に戻り値の「後半」を見つける

// 先にデフォルト値を設定して、値が見つかったら書き換える。
// 「国」の部分
const second_half = "planet Earth";
if(country) {
  second_half = county;
}
if(state && country === "USA") {
  second_half = state;
}

「前半」も同じように見つけられる

// 「都市」の部分
const first_half = "Middle-og-Nowhere";
if(state && country !== "USA") {
  first_half = state;
}
if(city) {
  first_half = city;
}
if(town) {
  first_half = town;
}

最後に情報を繋ぎ合わせる。

// placeの更新
return first_half + ", " + second_half;

これで、以下の4つのタスクが別々の領域として実行される

  1. location_infoディレクショナリから値を抽出する
  2. 「都市」の優先順位を調べる。何も見つからなかったら、デフォルトで「Middle-of-Nowhere」にする
  3. 「国」を取得する。なければ「Planet Earth」にする
  4. placeを更新する

12章 コードに思いを込める

おばあちゃんがわかるように説明できなければ、本当に理解したとは言えない。
- アルバート・アインシュタイン

誰かに複雑な考えを伝えるときには、細かいことまで話しすぎると相手を混乱させてしまう
自分の考えを凝縮して、最も大切な概念にすることが必要になる

本章では、コードをより明確にする簡単な手順を使う。

  1. コードの動作を簡単な言葉で同僚にもわかるよう説明する
  2. その説明の中で使っているキーワードやフレーズに注目する
  3. その説明に合わせてコードを書く

ロジックを明確に説明する

以下はあるWebページのPHPのコードの一部である。
ユーザーにページを閲覧する権限があるかどうかを確認して、もし権限がなければ、権限がないことをユーザーに知らせるページへ戻す。

$is_admin = is_admin_request();
if($document){
  if(!$is_admin && ($document['username'] != $_SESSION['username'])){
    return not_authorized();
  }
} else {
  if(!is_admin) {
    return not_authorized();
  }
}
// 引き続きページのレンダリング

このロジックを簡単な言葉で説明してみると...

権限があるのは、以下の2つ。
1)管理者
2)文書の所有者(文書がある場合)
その他は、権限がない。

以下は新しい解決策。

if(is_admin_request()) {
  // 権限あり
} elseif($document && ($document['username'] == $_SESSION['username'])) {
  // 権限あり
} else {
  return not_authorized();
}
// 引き続きページのレンダリング

このコードだと、if文の中身が2つもからになっている。しかし、コードは小さくなったし、ロジックも単純になった。否定形がなくなったため、こちらの方が理解しやすい。

13章 短いコードを書く

その機能の実装に悩まないで、きっと必要ないから

プログラマはプロジェクトに欠かせない機能を過剰に見積もることがある。その結果、多くの機能が完成しないか、全く使われないか、アプリケーションを複雑にするものになってしまう。
また、プログラマは実装にかかる労力を過小評価するものでもある。将来的に必要となる保守や文書化などの「負担」時間を忘れたりする。

質問と要求の分割

要求を詳しく調べれば、問題をもっと簡単にできることもある。そうすることで、必要なコードも少なくて済む。

(例)商用の店舗検索システム
要求は以下のようなものだ。
任意のユーザーの緯度経度に対して、最も近い店舗を検索する。

これを100%正しく実装するには、以下のことも考慮しなければいけない。

  • 日付変更線をまたいでいる時の処理
  • 北極や南極に近い時の処理
  • 「1マイルあたりの経度」に対応した地球の曲率の調整

これらを真面目に処理すれば、相当なコード量になる。
しかし、今回作るアプリケーションで扱うのは、アメリカのテキサス州にある30軒の店舗だけだとする。 その場合、上記3つの処理を考える必要はないので、要求から削除すればいい。

したがって、
テキサス州のユーザーのために、テキサスで最も近くにある店舗を検索する
といった問題を解決するための処理を考えればいい。
つまり、すべての店舗との距離を計算する。

まとめ

  • プロジェクト固有のコードから汎用コードを分離する
  • 複雑なロジックや考えは、より「簡単な言葉」で説明する
  • 不要な機能はプロダクトから削除する
  • 最も簡単に問題を解決できるような要求を考える

参考記事

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?