# リファクタリング 第5章、第6章 ## 第5章 カタログの紹介 この章には、6章以降のカタログの読み方と意図が記述されている。 * 名前:関数の抽出、クラスのインライン化等。本書内で頻繁に用いられる。 * スケッチ:リファクタリングのイメージを簡単な図で表している。 * 動機:リファクタリングすべき理由、あるいは避けるべき状況を示している。 * 手順:リファクタリングの手順を順を追って説明している。 * 例:リファクタリングの具体例をコードを使ってどのように行われるかを示している。 ## 第6章 リファクタリングはじめの一歩 ### 関数の抽出 **極めて頻繁に行われるリファクタリングである。** 最も代表的な理由は**意図と実装の分離**である。何をしているか分かる名前を付けて、まとまりとして抽出することによって、**関数の目的が良く分かる**ようになる。 関数の大きさはできるだけ小さくし、**細心の注意を払った命名**を行う必要がある。 #### 手順 1. 新しい関数を命名する。 2. 抽出するコードをコピーし関数内に張り付ける。 3. スコープ外となる変数を引数として渡す。 4. コンパイルし、抽出部分のコードを新しい関数の呼び出しに置き換える。 5. テストする。 6. (+α)類似するコードを探し、同様に関数抽出ができるかを検討する。 #### 例 * スコープ外の変数が無い場合 スコープ外変数が無く、同様の処理をまとめるだけであれば、抽出は極めて簡単である。 * スコープ外の変数がある場合(再代入無し) スコープ外変数があるが、形式が**再代入無し(const)** の場合は、**引数**に渡すだけで問題なく抽出できる。 * スコープ外の変数がある場合(再代入あり) スコープ外変数があり、形式が再代入あり(let)の場合は、**関数内部に一時変数として同様の変数を作り**(**変数の分離**)、値をreturnで返すことで、値を外部に戻す。 ### 関数のインライン化 **関数の抽出の逆**である。 関数を抽出しすぎた結果、関数としての役割が**他の関数への委譲がほとんど**の状態になってしまう場合、インライン化を行うべきである。 #### 手順 1. ポリモーフィズム系メソッドでは無いことを確認する。 2. 関数の呼び出し元を全て見つける。 3. 呼び出し元を中身で置き換える。 4. 1つ置き換えるごとにテストする。 5. 関数定義を取り除く。 #### 例 入り組んだケースでは、**ステートメント呼び出し側への移動**により、細かく手順を分ける必要がある。 ### 変数の抽出 **コード内の式に名前を付けたい時**、つまり**式が冗長すぎる時**や、**同じ式を再び利用したい時**に用いるリファクタリングである。 広いコンテキストで利用する場合は、関数やクラスにまとめて**問い合わせによる一時変数の置き換え**を利用すると良い。 #### 手順 1. 抽出しようとする式に副作用が無いかを確認する。 2. constで変数名と値を定義する。 3. 元の式を新しい変数に置き換える。 4. テストする。 ### 変数のインライン化 **変数の抽出の逆**である。 変数を定義して、直後の式に代入して終了という、**「定義しただけ」の一時変数**はコードの冗長化を招くため、インライン化を行うべきである。 #### 手順 1. 代入の右辺に副作用が無いかを確認する。 2. letであればconstに変更してテストする。(代入が1度のみであることを確認する) 3. その変数への最初の参照を探し、代入の右辺と置き換える。 4. テストする。 5. 3.を繰り返し、全ての参照箇所を更新する。 6. 変数宣言と代入を取り除く。 7. テストする。 ### 関数宣言(関数名)の変更 関数の**関数名及び引数名、または引数の数**を変更するリファクタリングである。 中でも引数の数については明確な基準は存在せず、悩ましいところである。 手順は簡易的な手順と、移行的手順の2つに分かれる。時と場合に応じて使い分ける。 #### 簡易的な手順 1. (パラメータ削除時)関数内部での参照が無いかを確認する。 2. 関数宣言を望ましいものに変更する。 3. 古い関数宣言のすべての参照を探し、新しいものに変更する。 4. テストする。 #### 移行的手順 1. 関数内部を適宜リファクタリングしておく。 2. 関数本体に**関数の抽出**を行い、新たな関数を作る。 3. パラメータの変更がある時は、簡易的な手順で変更する。 4. テストする。 5. 古い関数に対し**関数のインライン化**を行う。 6. 一時的な名前を**関数宣言の変更**で元に戻す。 7. テストする。 ### 変数のカプセル化 変数の扱える範囲が広いと、変更の際に広範囲に影響が出る。そこで**カプセル化を行い、関数経由でのアクセスに変更する。** 特にオブジェクト指向ではデータのプライベート化による**変更や参照の監視**ができるため必須となってくる。 #### 手順 1. カプセル化専用の関数を作る。 2. 静的チェックを実行する。 3. 一つ一つ変数をカプセル化関数の呼び出しに置き換える。 4. 置き換えるごとにテストする。 5. 変数の可視性を制限する。 6. テストする。 7. 変数がレコードの場合は、**レコードのカプセル化**を行う。 ### 変数名の変更 変数名は、どれだけ広い範囲で利用されるかに重要性が左右される。 1回の関数の呼び出しを超えて存続するフィールドは、注意深く名前をつける必要がある。 また、定数名はコピーすることで、古い名前から新たな名前へと参照を徐々に変更できる。 #### 手順 1. 変数が広く使われている場合、まずは**変数のカプセル化**を検討する。 2. 変数への参照を全て変更する。 3. テストする。 ### パラメータオブジェクトの導入 同一のまとまりをもつ「データの群れ」は、**単一のデータ構造に置き換え**た方が良い。パラメータ数を減らせるほか、一貫性の向上にも役立つ。 また、構造体の変化によるプログラムの概念的な構図を変化させる効果もある。 #### 手順 1. **関数宣言の変更**を施し、新たな構造体用のパラメータを追加する。 2. テストする。 3. 新たな構造体を正しいインスタンスに渡すようにすべての呼び出し側を修正する。一つ一つの呼び出し修正毎にテストする。 4. 元のパラメータを使用している箇所を、新たな構造体の要素を使うように置き換える。元のパラメータを削除してテストする。 ### 関数群のクラスへの集約 共通のデータに対して、互いに深いかかわりを持つ一群の関数があれば、**クラスを定義してまとめてしまう**と良い。 **関数群の変数への集約**との使い分けは、プログラムのより大きな文脈により決定される。 クラスを作る利点は、**利用者側がオブジェクトの核となるデータを変更でき、そこから派生したデータも一貫性が保たれる点である。** #### 手順 1. 関数間で共有しているデータのレコードに**レコードのカプセル化**を施す。 2. **関数の移動**を適用して、共通のレコードを扱う関数をそれぞれ新たなクラスへと移す。 3. データを操作するロジックの断片があればそれぞれ**関数の抽出**で抽出してクラスへと移す。 ### 関数群の変数への集約 **関数群への変数の集約**は、**データからの派生値を計算する処理をまとめたい時**に用いる。 データ変換関数としてまとめることで、派生値処理を全て行った計算結果を、1つの関数で受け渡しを行うことができる。 #### 手順 1. 変更されるレコードを入力として、同じ値を返す変換関数を作る。 2. ロジックを選んでその本体を変換関数側に移し、レコードに新たなフィールドを設ける。そのフィールドを使うようにクライアント側のコードを変更する。 3. テストする。 4. 関連した関数群について上記の手順を繰り返す。 ### フェーズの分離 一つのコードが異なる二つの処理を行っていた場合、別々のモジュールに分離するのが望ましい。 振る舞いを順次処理に分割することで、1つのモジュールで1つの処理を行う形にできる。 #### 手順 1. 後半部分のコードを**関数として抽出**する。 2. テストする。 3. 抽出した関数に追加される引数として、**中間データ構造**を導入する。 4. テストする。 5. 抽出した後半部分の各パラメータを確認し、前半との**共通部分がある場合は中間データ構造**へと移す。一つ移す毎にテストする。 6. 前半部分のコードに**関数の抽出**を施し、中間データ構造を返すようにする。 ###### tags: `読書`