# リファクタリング 第11章、第12章 ## 第11章 APIのリファクタリング ### 問い合わせと更新の分離 関数のうち、**値を問い合わせるものと、値を更新するものとを分離する**ことで、 どこでも利用できかつ観察可能な副作用を無くせる。 原則として値を更新する関数には、副作用があってはならない。 #### 手順 1. 関数をコピーして、問い合わせ用の名前をつける。 2. 新しい問い合わせ関数から、副作用を全て除去する。 3. 静的チェックを実行する。 4. 元の関数の呼び出しを調べる。戻り値を使っている場合は、その呼び出しを問い合わせ用の関数の呼び出しに置き換え、元の関数の呼び出しをその後ろに挿入する。(戻り値が副作用を利用していない場合。利用しているなら前に挿入) 5. 元の関数から戻り値を削除する。 6. テストする。 ### パラメータによる関数の統合 リテラル値が異なるだけの関数が複数存在する場合は、リテラル値を**パラメータ**として引数に持たせて**1つの関数**にすることで、**重複を排除**でき、関数の有用性も高まる。 #### 手順 1. 類似の関数のうち、1つを選ぶ。(通常中間の範囲から選択する) 2. 「関数宣言の変更」で、リテラル値をパラメータに変換する。 3. その関数を呼び出すすべてのところで、対応するリテラル値を渡す。 4. テストする。 5. 新しいパラメータを使用するように関数の本体を変更する。変更の度にテストする。 6. 類似の関数についても同様に、元の関数の呼び出しをパラメータ付き関数の呼び出しに置き換える。置き換える度にテストする。 ### フラグパラメータの削除 **フラグ引数**は、呼び出し元が呼び出し先に対してどのロジックを実行して欲しいかを指示するためのパラメータとして使われる。 フラグパラメータは、**関数呼び出しの多様性を隠してしまい**、さらに、フラグ引数にどの値を使えるかを知る必要があるため、**好ましくない。** #### 手順 1. パラメータに対し、明示的な関数を作成する。(明確な切替用の条件がある場合は「条件記述の分解」を行い、それ以外はラッピング関数を作成する。 2. パラメータにリテラル値を設定している呼び出し元を、対応する明示的な関数呼び出しに置き換える。 ### オブジェクトそのものの受け渡し レコードから値を取り出して関数に渡すコードは、**関数から直接呼出す**形に変更する。不要なパラメータが消え、**呼び出しの重複を防ぐ**ことができる。 #### 手順 1. 望ましいパラメータを持った空の関数を作る。 2. 新しく作った関数の本体を、新しいパラメータを古いパラメータに変換した上で、古い関数の呼び出しで埋める。 3. 静的チェックを実行する。 4. 新しい関数を使うように呼び出し元を調整し、変更の度にテストする。 5. 古い呼び出し元が全て変更されたら、古い関数に「関数のインライン化」を適用する。 6. 新しい関数とそのすべての呼び出し元の名前を変更する。 ### 問い合わせによるパラメータの置き換え パラメータリストは、関数の可変部分を縮約して、関数の振る舞いの主なバリエーションを表すようにするべきである。 パラメータを表す責務を関数内に入れることによって、シンプルになる。 なお、関数本体に望ましくない依存関係ができてしまう場合には、このリファクタリングは避けるべきである。 また、参照透過性(同じパラメータで呼び出した時に、常に関数が同じ振る舞いをすること)を失わないようにする必要がある。 #### 手順 1. 必要に応じてパラメータを算出している箇所に「関数の抽出」を適用する。 2. 関数本体でのパラメータの参照を、その値の取得する式への参照に置き換える。変更の度にテストする。 3. 「関数宣言の変更」を適用してそのパラメータを取り除く。 ### パラメータによる問い合わせの置き換え 関数のスコープ内でグローバル変数や取り除くべきものへの参照がある場合は、内部の参照をパラメータに置き換えて、責務を関数の呼び出し側に移す。 参照透過性(同じパラメータで呼び出した時に、常に関数が同じ振る舞いをすること)が失われないよう、適切な要素の移動が必要がある。 呼び出し側に責務を押し付けすぎて本末転倒とならないように注意深く行う。 #### 手順 1. 問い合わせを行っている箇所に「変数の抽出」を適用して、関数本体の残りの部分から分離する。 2. 問い合わせ以外の関数本体のコードに「関数の抽出」を適用する。 3. 変数のインライン化を適用して、先ほど作成した変数を取り除く。 4. 元の関数に「関数のインライン化」を適用する。 5. 新しい関数の名前を元の関数の名前に変更する。 ### setterの削除 オブジェクトを生成した後に、その値を変更したくない場合は、setterを削除することで変更不可となる。 #### 手順 1. 設定したい値がコンストラクタに渡されない場合は「関数宣言の変更」を適用して、パラメータを追加する。 2. コンストラクタにsetterの呼び出しを追加する。 3. コンストラクタ以外でのsettterの呼び出しを一つ一つ取り除いて、新しいコンストラクタを呼び出すようにする。取り除く度にテストする。 4. setterに対して「関数のインライン化」を適用する。可能であればフィールドを変更不可とする。 5. テストする。 ### ファクトリ関数によるコンストラクタの置き換え Javaのコンストラクタは、呼び出したクラスのインスタンスを返さなければならず、それを環境やパラメータに応じてサブクラスやプロキシで置き換えることはできない。 また、通常の関数が期待される文脈でコンストラクタを使うのは困難である。 こうした制約のないファクトリ関数でコンストラクタを置き換える。 #### 手順 1. ファクトリ関数を作成する。その関数の本体でコンストラクタを呼び出す。 2. 既存のコンストラクタの呼び出しを、一つ一つファクトリ関数の呼び出しに置き換える。 3. 変更の度にテストする。 4. コンストラクタの可視性をできる限り制限する。 ### コマンドによる関数の置き換え 関数自身をオブジェクトとしてカプセル化を行うことが有用な場合があり、そのようなオブジェクトを「コマンドオブジェクト」あるいは「コマンド」という。 コマンドは単なる関数の仕組みと比べて、関数の制御と表現において高い柔軟性がある。 第一級関数とコマンドかの選択を迫られた時は、95%の確率で関数を選ぶが、簡単なアプローチでは実現できない機能が特別に必要な時は、**コマンドに置き換える。** #### 手順 1. 関数のための空クラスを作り、関数にちなんだ名前をつける。 2. 「関数の移動」を適用して。関数を空のクラスに移動する。 3. 引数ごとに対応するフィールドを作り、パラメータをコンストラクタに移動することを検討する。 ### 関数によるコマンドの置き換え コマンドは強力だが、コストが伴う。多くの場合では関数を呼び出して仕事をさせたいだけにとどまる。こうした場合は通常の関数に変更する。 #### 手順 1. コマンドの生成とその実行メソッドの呼び出しに対して「関数の抽出」を適用する。 2. コマンドの実行メソッドから呼び出されるメソッドに対して、それぞれ「関数のインライン化」を適用する。 3. 「関数宣言の変更」を適用して、コンストラクタのすべてのパラメータを、コマンドの実行メソッドのパラメータに加える。 4. フィールドごとに、コマンドの実行メソッド内での参照の代わりにパラメータを呼び出すように変更する。変更の度にテストする。 5. コンストラクタの呼び出してコマンドの実行メソッドの呼び出しを、呼び出し元にインライン化する。 6. テストする。 7. 「デッドコードの削除」をコマンドクラスに適用する。 ## 第12章 継承の取り扱い ### メソッドの引き上げ 複数のサブクラスに重複するメソッドが存在する場合は、**スーパークラスにメソッドを引き上げ、重複を排除する。** 他のリファクタリングのステップを踏んだ後に行うことが多い。 #### 手順 1. 引き上げたいメソッドが同一であるかを精査する。 2. メソッド本体内のすべての呼び出しとフィールドの参照が、スーパークラスから呼び出し可能な特性を参照していることを確認する。 3. メソッドのシグネチャーが異なる場合は、「関数宣言の変更」を適用して、スーパークラスで使いたい形に変更する。 4. スーパークラスに新しいメソッドを作成する。いずれかのメソッド本体をそこにコピーする。 5. 静的チェックを実行する。 6. 一つのサブクラスのメソッドを削除する。 7. テストする。 8. サブクラスのメソッドが消えるまで、削除を繰り返す。 ### フィールドの引き上げ 複数のサブクラスで同じようにフィールドが使われている場合も、**スーパークラスにフィールドを引き上げ、重複を排除する。** #### 手順 1. 引き上げたいフィールドを使っている呼び出し元が、全て同じやり方でフィールドを利用しているかどうか詳しく調べる。 2. フィールド名が異なる場合は、「フィールド名の変更」を適用して同一名にする。 3. スーパークラスに新しいフィールドを一つ作成する。 4. サブクラスのフィールドを削除する。 5. テストする。 ### コンストラクタ本体の引き上げ コンストラクタには、何をどの順序で行うかの特別なルールがあり、共通の振る舞いをスーパークラスに移すためには、**コンストラクタ本体を移動**させる。 #### 手順 1. スーパークラスのコンストラクタが存在しなければ、コンストラクタを定義する。サブクラスのコンストラクタがそれを呼び出せるようにする。 2. 「ステートメントのスライド」を適用し、superの呼び出しの直後に、共通のステートメントを移動する。 3. 共通なコードをサブクラスから削除し、スーパークラスに入れる。共通なコードが参照するパラメータを、スーパークラスのコンストラクタに追加する。 4. テストする。 5. コンストラクタの先頭に移動できない共通コードがある場合は、「関数の抽出」を行ってから、「メソッドの引き上げ」を適用する。 ### メソッドの押し下げ あるメソッドが一つのサブクラスに対しての処理のみを行っている場合、スーパークラスから**サブクラスへと処理を移動する。** 呼び出し先が**特定のサブクラスであることを呼び出し側が分かっている**ことが前提となる。他の場合は「ポリモーフィズムによる条件記述の置き換え」を行う方が良い。 #### 手順 1. 対象となるメソッドを、それを必要とする全てのサブクラスにコピーする。 2. スーパークラスからそのメソッドを削除する。 3. テストする。 4. そのメソッドを必要としないすべてのサブクラスからそのメソッドを削除する。 5. テストする。 ### フィールドの押し下げ メソッドの押し下げと同様に、あるフィールドが一つのサブクラスに対しての処理のみを行っている場合に、**サブクラスへと処理を移動する。** #### 手順 1. 対象となるフィールドを、それを必要とする全てのサブクラスにコピーする。 2. スーパークラスからそのフィールドを削除する。 3. テストする。 4. そのメソッドを必要としないすべてのサブクラスからそのフィールドを削除する。 5. テストする。 ### サブクラスによるタイプコードの置き換え **タイプコード**とは、様々なバリエーションを持つ要素に対する、バリエーションの種類を記入したものである。 このタイプコードはポリモーフィズムによる柔軟な振る舞い変更ができる場合や、特定の場合のみ有効なフィールドやメソッドがある場合に有効である。 いずれも、**代わりにサブクラスを配置する**ことによって振る舞いの変化を実現できる。 #### 手順 1. タイプコード用フィールドを自己カプセル化する。 2. タイプコード値を一つ選択し、そのタイプコード用のサブクラスを作成する。 3. 選択したタイプコードのgetterを上書きして、リテラルのタイプコード値を返すようにする。 4. タイプコードのパラメータから、新しいサブクラスに変換するための選択ロジックを作成してテストする。 5. タイプコードの値ごとに、サブクラスの作成と選択ロジックの追加を繰り返す。変更の度にテストする。 6. タイプコード用フィールドを削除する。 7. テストする。 8. タイプコードのアクセサを使用するすべてのメソッドに対し「メソッドの押し下げ」及び、「ポリモーフィズムによる条件記述の置き換え」を適用する。すべてを置き換えたら、タイプコードのアクセサを削除する。 ### サブクラスの削除 サブクラスの存在価値が無くなってしまった場合は、サブクラス自体を削除する。 #### 手順 1. 当該サブクラスのコンストラクタに、「ファクトリ関数によるコンストラクタの置き換え」を適用する。 2. サブクラスのタイプ判定をしているコードがあれば、「関数の抽出」、「関数の移動」を適用してスーパークラスに移す。変更の度にテストする。 3. サブクラスのタイプを表すフィールドを作成する。 4. サブクラスを参照しているメソッドを、この新しいタイプフィールドを使用するように変更する。 5. サブクラスを削除してテストする。 ### スーパークラスの抽出 二つのクラスで重複する振る舞いがある場合、重複箇所をスーパークラスに移し、元々の二つのクラスをサブクラスとして継承する。 ### 手順 1. 空のスーパークラスを作成して、元のクラス群をそのサブクラスとする。 2. テストする。 3. 一度に一つずつ「コンストラクタ本体orフィールドorメソッドの引き上げ」を適用して、共通の要素をスーパークラスに移す。 4. サブクラスの残りのメソッドを調べて、共通部分があるかを確認する。あれば「関数の抽出」を適用した後で、「メソッドの引き上げ」を適用する。 5. 元のクラスの呼び出し側を調べる。スーパークラスのインターフェースを使用するように調整するかを検討する。 ### クラス階層の平坦化 クラス階層のリファクタリングにより、特性を上げ下げした結果、継承関係が消えてしまっている場合がある。こうした場合、スーパークラスとサブクラスを一つにまとめてしまう。 #### 手順 1. 削除するクラスをどちらにするか決める。 2. 「フィールドorメソッドの引き上げor押し下げ」を適用し、全ての要素を一つのクラスに移動する。 3. 削除するクラスへの参照を調整して、存続するクラスへの参照へと変更する。 4. 空になったクラスを削除する。 5. テストする。 ### 委譲によるサブクラスの置き換え クラスの継承関係の欠点は、軸が一つしかない点である。振る舞いを変える基準が複数あると、継承ではさばき切れない。 また、親子がクラス間で極めて密接している場合、スーパークラスの変更がサブクラスを壊してしまうリスクが高くなる。 これらの問題に対処する方法として**委譲**がある。サブクラス化よりも依存関係ははるかに弱くなり、通常のクラス関係に戻る。この結果、継承の欠点をカバーできる。 そこで、サブクラスを委譲の関係に置き換える。 #### 手順 1. コンストラクタの呼び出し元が多い場合は、「ファクトリ関数によるコンストラクタ置き換え」を適用する。 2. 委譲用の空のクラスを作成する。コンストラクタでサブクラス固有のデータやスーパークラスへの逆参照を受け取る。 3. 委譲先のオブジェクトを保持するフィールドを、スーパークラスに追加する。 4. サブクラスの生成処理を変更して、委譲用フィールドを委譲先クラスのインスタンスで初期化する。 5. 委譲先クラスに移動するサブクラスのメソッドを選択する。 6. 選択したメソッドに「関数の移動」を適用して、委譲先クラスへと移す。なお、元の委譲を行うためのコードは削除しないこと。 7. 元のメソッドにクラス外からの呼び出しがある場合は、元の委譲を行うためのコードをスーパークラスに移して、委譲先の存在を確認するガード節を置く。無ければデッドコードを削除する。 8. テストする。 9. サブクラスのすべてのメソッドが移動されるまで繰り返す。 10. サブクラスのコンストラクタの呼び出し元を全て見つけて、スーパークラスのコンストラクタを使用するように変更する。 11. テストする。 12. サブクラスのデッドコードを削除する。 ### 委譲によるスーパークラスの置き換え スーパークラスの機能がサブクラスで役割を果たせていなかったり、スーパークラスを使用するすべてのケースで有効となっていない場合は、混乱と誤りを招きやすい。委譲によってスーパークラスを置き換える。 #### 手順 1. サブクラスに、スーパークラスのオブジェクトを参照する委譲用フィールドを作る。このフィールドには、初期化時にスーパークラスの新しいインスタンスの参照を格納する。 2. スーパークラスの要素(フィールドとメソッド)ごとに、委譲先に転送する転送用関数をサブクラス内に作る。ひとまとまりの転送用関数を作るたびにテストする。 3. 全てのスーパークラスの要素を転送用関数でオーバーライドしたら、継承関係を削除する。 ###### tags: `読書`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up