# レガシーコード改善ガイド 第3部① 依存関係を排除する方法 ## 第25章 依存関係を排除する方法 ### 25.1 パラメータの適合 必要なパラメータに対するインターフェースを新規作成し、実装する本番用クラスと擬装クラスを作成して依存関係を排除する。 シグネチャの維持が不可能なため注意が必要である。 #### こんな場合に役立つ * インターフェースの抽出が難しい場合。(非常に低レベルの仕組みに依存している、特定の実装技術に特化している等) * パラメータの擬装が難しい場合。 #### 手順 1. メソッドが使用するインターフェースを新規作成する。ポイントは分かりやすく単純で、メソッドに大幅な変更を与えないこと! 2. インターフェースを実装する本番用クラスを作成する。 3. 同様に擬装クラスを作成する。 4. 擬装クラスのオブジェクトを渡して実行できるテストケースを作る。 5. メソッドのパラメータを置き換える。 6. テストを実行する。 ### 25.2 メソッドオブジェクトの取り出し 単一の大きなメソッドからメソッドオブジェクトを抽出してクラスを作る。 単純な場合は切り出しが容易だが、他のクラスのメソッドを使用している場合は、インターフェースの抽出を組み合わせなければ排除できない場合もある。 #### こんな場合に役立つ * メソッドが巨大な場合。 * インスタンス変数や他のメソッドを使用している場合。 #### 手順 1. メソッドのコードを移動するためのクラスを新規作成する。 2. 新クラスのコンストラクタに、引数リスト又は関数の参照をコピーする。コピー時はシグネチャの維持を行う。 3. コンストラクタの引数ごとにインスタンス変数を宣言する。同様にシグネチャの維持を行う。 4. 新クラスに空の実行用メソッドを作成する。 5. 古いメソッドを実行用メソッドに移す。 6. コンパイラまかせでコンパイルする。メソッド内変数の呼び出しが元のクラスの参照のためエラーが起きるので、一つ一つ新クラスのものに修正していく。 7. privateメソッドや、インスタンス変数の参照を行う際は、public化やgetメソッドの作成を行う。 8. 新クラスをコンパイルする。 9. 元のクラスで新クラスのインスタンスを作成し、そこに処理を委譲する。 10. 必要があればインターフェースの抽出を行う。 ### 25.3 定義の補完 型宣言と定義を異なる場所で行い、依存関係を排除する。 主にC言語、C++向けで、型宣言はテストファイル、定義は実装ファイルにて行うことが多い。 但し、実行ファイルが別に必要であったり、1メソッド2定義となりメンテナンス上の負担が増大したりとデメリットが多いため、使用は最小限にとどめるべきである。 #### こんな場合に役立つ * C言語で、同一メソッド内で定義を変更して依存関係を排除したい場合。 * インスタンス変数や他のメソッドを使用している場合。 #### 手順 1. 置き換えたい定義のあるクラスを特定する。 2. メソッドの定義がソースファイル上にあることを確認する。 3. テスト用ソースファイルで、ヘッダーファイルをincludeする。 4. 元のソースファイル定義がビルドに含まれていないことを確認する。 5. ビルドを行い、欠落したメソッドを見つけてテスト用ソースファイルに追加する。 ### 25.4 グローバル参照のカプセル化 グローバル要素をカプセル化して、参照を分断する。 データや小さいメソッドから着手する。 #### こんな場合に役立つ * グローバル要素に依存関係がある場合。 * 関数が多くのメソッドやクラスから呼び出されている場合。 #### 手順 1. カプセル化したいグローバル要素を特定する。 2. カプセル化用の新クラスを作成する。 3. 新クラスにグローバル要素をコピーする。変数が含まれている場合は初期化する。 4. 元のグローバル要素の宣言をコメントアウトする。 5. 新クラスのグローバルインスタンスを宣言する。 6. コンパイラまかせでコンパイルする。古いグローバル要素に対する未解決の参照エラーを見つける。 7. 新クラスのグローバルインスタンスの名前を、未解決の参照の先頭にそれぞれ付与する。 8. 必要があれば、静的setメソッドの導入や、コンストラクタのパラメータ化、getメソッドによるグローバル参照の置き換えを行う。 ### 25.5 静的メソッドの公開 メソッドをpublicかつstaticとして、他クラスから参照できるようにする。 staticメソッドはインスタンス化する必要がないため、あるべきクラスが特定できない場合には役立つ。 #### こんな場合に役立つ * 他クラスからの参照が多いメソッドの場合。 * インスタンス化の依存関係を排除したい場合。 #### 手順 1. static化して公開したいクラスのメソッドにアクセスするテストを記述する。 2. メソッドの本体をstaticメソッドとして抽出する。シグネチャの維持を忘れずに行う。 3. 新たにメソッドを命名する場合は、パラメータの名前が役に立つことが多い。 4. コンパイラまかせでコンパイルする。インスタンスメソッドへのアクセスエラーが起きた場合は、それらもstaticにできるかを調べる。できる場合はstaticにする。 ### 25.6 呼び出しの抽出とオーバーライド メソッドの呼び出しを別メソッドとして抽出し、テスト用サブクラスでオーバーライドして依存関係を排除する。 とても便利なリファクタリングで、多くのリファクタリングツールがサポートしている。 #### こんな場合に役立つ * 依存関係が局所的で少ない場合。 * グローバル変数やstaticメソッドへの依存関係を排除したい場合。 #### 手順 1. 抽出したい呼び出しを特定して、そのメソッドの宣言を見つける。 2. シグネチャの維持を行いながら、そのメソッドのシグネチャをコピーする。 3. 新メソッドを定義し、シグネチャをペーストして定義する。 4. 元のメソッドの呼び出し部分を新メソッドのものに置き換える。 5. テスト用サブクラスを導入して、新しいメソッドをオーバーライドする。 ### 25.7 ファクトリメソッドの抽出とオーバーライド ファクトリメソッドと呼ばれる、コンストラクタの代替を果たすメソッドを用いて、本番用とテスト用で初期化処理を分離して依存関係を排除する。 仮想関数を利用したリファクタリングのため、仮想関数をサポートしていない言語(C++等)では利用できない。 #### こんな場合に役立つ * 本番用とテスト用で、初期化の振る舞いを変えたい場合。 * 仮想関数が利用できる言語を使用している場合。 #### 手順 1. コンストラクタ内のオブジェクト生成処理を特定する。 2. 特定した生成処理全てをファクトリメソッドとして抽出する。 3. テスト用サブクラスを導入して、ファクトリメソッドをオーバーライドする。 ### 25.8 getメソッドの抽出とオーバーライド メソッドの実装部分をgetメソッドの呼び出し先として抽出し、テスト用サブクラスでgetメソッドをオーバーライドして依存関係を排除する。 getメソッドの戻り値のオブジェクトを最初の呼び出しで生成する、遅延getメソッドも使える。 ファクトリメソッドと同様に仮想関数をサポートしていなければ使えず、ガベージコレクション機能が無い言語では、オブジェクトの生存期間に注意を払わなくてはならない。 #### こんな場合に役立つ * 仮想関数が利用できる言語を使用している場合。 * ガベージコレクションで自動的にメモリ解放を行う機能を有する言語を使用している場合。 #### 手順 1. getメソッドを必要とするオブジェクトを特定する。 2. オブジェクト生成に必要なロジックを全て抽出して、getメソッドに入れる。 3. オブジェクトを利用している箇所を、getメソッドの呼び出しにすべて置き換える。 4. 全てのコンストラクタで、オブジェクトの参照をNullで初期化する。 5. getメソッドに初回呼び出し時のロジックを追加して、オブジェクトの参照がnullの時にオブジェクトを生成して参照に割り当てる。 6. そのクラスをサブクラス化して、getメソッドをオーバーライドしてテストで使用する代替オブジェクトを提供する。 ### 25.9 実装の抽出 フィールド及びpublicメソッドのみをインターフェースとし、具象メソッドをインターフェースを実装したクラスに抽出する。 インターフェースとクラスのネーミングには注意を払う必要がある。 # こんな場合に役立つ * インターフェースを用いて、抽象メソッドと具象メソッドを分離したい場合。 #### 手順 1. 元のクラス宣言をコピーして、異なる名前をつける。 2. 全てのpublic以外のメソッドと変数を削除して、インターフェースとする。 3. 残ったpublicメソッドは抽象メソッドとして、abstructをつける。 4. インターフェースの不要なimport文やinclude文を削除する。 5. 本番用クラスに新しいインターフェースを実装させる。 6. 本番用クラスをコンパイルして、インターフェースで定義したメソッドが全て実装されているかを確認する。 7. システムの残りの部分をコンパイルして、元のクラスのインスタンスを生成している箇所を、新しい本番用クラスに置き換える。 8. 再コンパイルしてテストする。 ### 25.10 インターフェースの抽出 インターフェースに特定の状況で使用したいメソッドを抽出して、本番用クラスとテスト用の擬装クラスを作り、依存関係を排除する。 実装の抽出とは異なり、全てのpublicメソッドを抽出する必要は無い。必要なもののみを抽出する。 非仮想関数には向かない。 # こんな場合に役立つ * インターフェースを用いて、擬装オブジェクトを作成したい場合。 #### 手順 1. 適切な名前の空のインターフェースを作成する。 2. 抽出対象のクラスでそのインターフェースを実装する。 3. 該当するオブジェクトの使用箇所を変更して、元のクラスの代わりにインターフェースを用いるようにする。 4. システムをコンパイルする。コンパイルエラーが報告したメソッド呼び出しに対して、新しいメソッド宣言をインターフェースに追加する。 ### 25.11 インスタンス委譲の導入 staticメソッドにおいて依存関係がある場合に、委譲用のインスタンスメソッドを導入して、メソッド呼び出しを変更する。 数個程度であれば委譲メソッドが役立つが、多数ある場合は全てをインスタンスメソッドに移してしまった方が良い。 # こんな場合に役立つ * 数個程度のstaticメソッドの依存関係を、インスタンスメソッドで置き換えたい場合。 #### 手順 1. テスト環境で使用すると問題のあるstaticメソッドを特定する。 2. 特定したメソッド用のインスタンスメソッドをそのクラスに作成する。シグネチャの維持を忘れずに行い、処理をstaticメソッドに移譲する。 3. テストで保護したいクラスの中で、staticメソッドを呼び出している場所を見つける。 4. メソッドのパラメータ化等を用いて、staticメソッドの呼び出しにインスタンスを渡す。 5. 元のstaticメソッドの呼び出しを、委譲用のインスタンスメソッドの呼び出しで置き換える。 ### 25.12 静的setメソッドの導入 Singletonデザインパターンを使用したクラスに静的setメソッドを導入して依存関係を排除する。 # こんな場合に役立つ * Singletonデザインパターンを置き換えたい場合。 #### 手順 1. Singletonクラスのコンストラクタの保護レベルをprivate→protectedとして、サブクラス化による擬装クラスの生成を可能とする。 2. Singletonクラスに、静的setメソッドを追加する。そのsetメソッドでは、Singletonクラスの参照を受け取るようにする。 3. setメソッド内で新しいオブジェクトを設定する前に、Singletonオブジェクトを確実に破棄する。 4. テストの準備のためにSingletonのprivateあるいはprotectedメソッドにアクセスする必要がある場合には、Singletonをサブクラス化するか、インターフェースを抽出してそのインターフェースを実装するオブジェクトの参照をSingletonで保持する。 ###### tags: `読書`