# リーダブルコード 第Ⅲ章 コードの再構成 ## 10章 無関係の下位問題 大きな問題を小さく分割する方法の一つとして、「**コードの大きな目標とは無関係な一連のプロセスを抽出する**」方法が挙げられる。 これは、大きな目標を達成するための小さな目標達成の処理を、まとめて一つの関数に抽出する方法である。大きな目標に直接的に効果があるかを確認して、抽出していく。 すなわち、**プロジェクト固有のコードから、汎用コードを分離することである。** ### 10.1 入門的な例:findClosestLocation このプログラムの大目標:与えられた地点から最も近い場所を見つける。 小目標:2地点の値をラジアンに変換して定理を用いて、球体距離を算出する。 小目標がfor文で囲まれているため、この部分を抽出してspecial_distanceという関数として扱う。 小目標=無関係の下位問題が抽出されると以下のメリットがある。 * **コードが読みやすくなる**:高レベルな大目標に集中できる。 * **再利用可能となる**:関数を呼び出して別の項目で再利用できる。 ### 10.2 純粋なユーティリティコード 大目標:ファイルの中身の文面を利用した処理を行う。 小目標:ファイルの中身を全て読み込む。 小目標の機能をReadFileToString()等の関数に置き換える。 C++のようにファイル読み込みの関数が無い言語の場合、自分で処理を作っておき関数化すれば、他ファイルでも扱うことが可能となる。 →**ユーティリティコードの自作** ### 10.3 その他汎用コード 大目標:サーバをAjaxで呼び出してレスポンスを処理する。 小目標:ディクショナリをキレイな文面で印字する。 大目標に直接寄与しない、ディクショナリの印字における文面調整コードが入っているため抽出する。ここではformat_pretty(obj)として抽出した。 小目標を関数として独立させた結果、例外処理やネストしたオブジェクトの処理などの**追加処理が簡単に加えられる。** ### 10.4 汎用コードをたくさん作る 10.3のように、関数として独立させた汎用コードは、**プロジェクトとは完全に切り離されている**ため、**プロジェクトによらず用いることができる優れものとなる。** コードを切り離せば、残ったコードは小さくなり考えやすくなる。 ### 10.5 プロジェクトに特化した機能 大目標:名前をURL形式でデータベースに保存する。 小目標:名前を有効なURL形式に変換する。 ここではURL変換プロセスを切り離し、make_url_friendly(text)と名付けている。残ったコードには規則性のあるパターンのみが残った。 なお、切り離したコードを別途データを作って別ディレクトリ(汎用ディレクトリ)に入れるかどうかはその時々次第である。 ### 10.6 既存のインターフェースを簡潔にする JavaScriptでクッキーを読み込む場合、自力でクッキーを読み込む必要がある。関数get_cookie()を作成し、max_resultsを抽出する。 既存のインターフェースが理想的なインターフェースとは程遠い場合でも、妥協することは無い。 ### 10.7 必要に応じてインターフェースを整える 大目標:ユーザー情報を暗号化してURLに含める。 小目標:PythonオブジェクトをURLセーフな文字列にする。 ここでは暗号化したURLの文字列にするための処理が大半となっており、関数で抽出する。すると、本質的なロジックはかなり簡潔になる。 ### 10.8 やりすぎに注意 無関係の下位問題があるからと言って、何でもかんでも抽出して関数を作れば良いわけではない。**小さい関数を作りすぎると逆に追いかけづらくなる。** あくまで新しい関数を追加した際の読みにくさが増えるコストを相殺したり、再利用可能にしたい場合の手段であり、 **抽出が目的になってはならない。** ## 11章 一度に1つのことを 1つのコードに複数の処理を行うようなコードは理解が難しい。 **一度にタスクは1つだけ行う。** この章では、コードにおける**フラグメンテーションの解消(デフラグ)** について学ぶ。 ### 11.1 タスクは小さくできる ここで扱う投票用ウィジェットはUP/DOWNボタンのクリックに応じて、評価の数値(score)が変動するものである。 このプログラムのタスクは以下のとおりである。 1. old_vote(古い投票)とnew_vote(新しい投票)を数値に変換する。 2. scoreを更新する。 この2つのタスクを別々に解決すれば、理解しやすいコードになる。 ### 11.2 オブジェクトから値を抽出する ここで扱う所在地表示システムは、米国の場合「市町名・都市名・州名・国名」を扱い、適切な地域名と国名を示すものである。 地域名は市町村名→都市名→州名の優先順位で、代表1つを抽出する。 unknown時のデフォルト値は地域名:Middle-of-Nowhere, 国名:Planet Earthとする。 このプログラムのタスクは以下のとおりである。 1. location_infoから値を抽出する。 2. 都市の優先順位を調べる。 3. 国名を取得する。 4. placeを更新する。 1.はそのまま抜き出す。2.は前半項目にどの名前を書くべきか判定する。 3.は後半項目にどのような国名を使うかを判定する。2.と3.の結果を4.で足し合わせる。 また、追加として米国・非米国の区別が加わったが、州名が無い非米国の処理の扱いは、米国とは別に扱うと良い。 ### 11.3 もっと大きな例 ここで扱うWeb統計情報の更新システムは、HTTPリクエストやコンテントタイプ等の更新をチェックして反映するものである。 こちらは先ほどよりもさらに複雑になっている。必要なタスクは以下の4つである。 1. キーのデフォルト値はunknownを用いる。 2. HttpDownLoadのメンバがあるかを確認する。 3. 値を抽出して文字列に置き換える。 4. counts[]を更新する。 こちらもまずキーのデフォルト値を設定しておき、その後、if文でメンバがあるかを一つずつ確認し、文字列に置き換える。 置き換えた文字列は最後にcountsを[]更新して有効となる。 各領域に分けておくことでそれぞれが分離し、他の領域を考えなくて済む。 #### さらなるカイゼン 11章ではタスクごとにコードを分割することを行ってきたが、ヘルパー関数を用いて読みやすさを向上させる取り組み(9章参照)など、 問題を異なる方向で「スライス」して、どちらも意味のあるカイゼンが可能となる。 ## 12章 コードに想いを込める **物事を理解しているとは、他の人にも説明できる状態であることを意味する。** プログラマーでないような人にも分かるよう説明するには、**「簡単な言葉」で説明する必要がある。** ### 12.1 ロジックを明確にする ロジックが沢山あると理解しにくい。このようなコードを解くには、**簡単な言葉でロジックを説明する必要がある。** この例の場合、権限が付与される2パターンに着目して、否定形のない簡潔なif文に起こすことでロジックを単純化させている。 ### 12.2 ライブラリを知る ロジックをが分かると、ライブラリをより活用できる。 10個のヒントからランダムで1種類を表示するページの場合、 1. 今見えているヒントを隠す 2. 次のヒントを見つけて表示する。 3. ヒントが無くなったら最初のヒントに戻る。 と**ロジックを明確にすることで、ライブラリが提供する機能を生かしたコードへ**と生まれ変わった。 ### 12.3 この手法を大きな問題へと適用する 3つに分かれた表から同じtimeのものだけをJOINするプログラムの場合、次のような説明ができる。 1. 3つの行のイテレーターを一度に読み込む。 2. 行のtimeが一致していなければ、一致するまで行を進める。 3. 一致した行を印字して行を進める。 4. 一致する行が無くなるまで繰り返す。 ここでは、AdvanceToMatchingTime関数に一致するまで行を進めるコードを抽出し、すっきりしたコードが実現した。 #### 手法を再帰的に適用する 抽出したAdvanceToMachingTime関数内の機能を、同様に言葉で説明してみる。 1. 現在の行のtimeをチェックし、一致していれば終了する。 2. 一致していなければ、遅れている行を進める。 3. 行が一致するまでこれを繰り返す。 ここでは、t1,t2,t3とそれぞれの表の時間を、簡単な変数で置き換えて読みやすくした。 また、終了条件を明確にし、それ以外ならば遅れている行を進めていくコードとなり、非常に読みやすくなった。 ## 13章 短いコードを書く **最も読みやすいコードは、何も書かれていないコードである。** プログラマーにとって最も大切な技能は、**コードを書かない時を知る**ことかもしれない。 ### 13.1 その機能の実装に悩まないで プログラマーは**欠かせない機能を過剰に見積もりすぎてしまいやすい。** **実装に懸かる労力を過小評価**し、時間を楽観的に見積もったり、保守や文書化等の負担を忘れたりする。 ### 13.2 質問と要求の分割 #### 要求範囲に合わせる **すべてのプログラムで、あらゆる要件に対して100%正確結果を返すコードを実装する必要は無い。** システムに必要とされる範囲が部分的で良いのであれば、わざわざあらゆる要件に対応するものを作る必要はない。 #### 顧客の環境に合わせる **理論上最適な方式が、あらゆる場面で必ず最適であるとは限らない。** 顧客の環境に合わせた方式で実装する。 例ではキャッシュの実装について、LRU方式のキャッシュが最適解だと考えていたが、アクセスが必ず順番であったことから単項目キャッシュへと変更している。 ### 13.3 コードを小さく保つ コードが小さいうちは関数の定義や呼び出し、コードのコンパイルや実行は簡単であるが、プロジェクトが進みファイルが増えると、どんどん困難になっていく。 システムの成長は、結びつける複雑さをもっと速い速度で成長させるのである。 **プロジェクトが成長しても、コードはできるだけ小さく軽量に維持するしかない。** そのための方策としては、この第Ⅲ部で挙げた作戦の他に、未使用のコードの排除、サブプロジェクトへの分割、コードの重量を意識する等がある。 ### 13.4 身近なライブラリに親しむ **標準ライブラリを一読することは、ライブラリの活用に役立つ。** ライブラリには効果的な機能が搭載されていることも多く、どんな機能があるのかをざっと読んで思い出せるようにしておけば役に立つ。 ライブラリは、膨大な過程を経て作成され、進化を生き延びてきたコードであるため、大きな価値がある。 これらのライブラリの再利用は良いことである。 ### 13.5 Unixでエラーを見つける unixを使えば、HTTPレスポンスのエラーを見つけるコードは非常に簡単に入力できる。 本物のコードを書かなくても、容易にエラーを見つけることができる。 ###### tags: `読書`