# レガシーコード改善ガイド 第2部② ※依存関係の解消のための技法は、[第25章](https://hackmd.io/GXDkn0F5S5C1pa2TXn6ZOw#レガシーコード改善ガイド-第3部-依存関係を排除する方法)を参照のこと。 ## 第9章 このクラスをテストハーネスに入れることができません 一般的な原因として以下の4つが考えられる。 1. クラスのオブジェクトを簡単に作成できない。 2. そのクラスを含むテストハーネスを簡単にビルドできない 3. 使用しなければならないコンストラクタが、悪い副作用を持つ。 4. かなりの処理量がコンストラクタで行われ、その内容を検出する必要がある。 ### 9.1 苛立たしいパラメータ パラメータが、DBなどの実装に依存している場合である。 代表的な解決策として、**インターフェースの抽出**を行い、**擬装オブジェクトを作成する**ことがあげられる。 また、パラメータに**nullを渡す**ことで、実質的な影響を与えないようにすることもできる。 なお、テストコードは、本番コードと同じルールに従う必要はない。本番コードではご法度でも、テストコード内であれば許容されることが多い。 ### 9.2 隠れた依存関係 コンストラクタの内部に依存関係が隠れている場合である。 **コンストラクタのパラメータ化**を用いて、コンストラクタで行っていたオブジェクト作成を、引数で与える形に変更することで、インターフェースの抽出に繋がっていく。 コンストラクタ周りの依存関係の解決において最も手軽である。 ### 9.3 複雑な生成 コンストラクタ内で非常に多くのオブジェクト生成をしていたり、非常に多くのグローバル変数にアクセスしている場合は、コンストラクタのパラメータ化が使えない。 この場合は、**FactoryMethodの抽出とオーバーライド**、**インスタンス変数の入れ替え**などの技法が使える。 但し、C++においては、コンストラクタ内でのオーバーライドが不可能であり前者は使用不可である。 また、後者ではガベージコレクションが無いため、オブジェクトの入れ替え時に古いオブジェクトの開放を手動で行う必要があり、注意が必要である。 その他の言語においては両者とも可能である。 ### 9.4 苛立たしいグローバルな依存関係 グローバル変数が使われている場合、上記で紹介した方法が使えないことが多い。 グローバル変数は不透明さや依存関係の大きさの観点から問題があり、使わないことが推奨されるが、レガシーコードでは利用されている場合がある。 解決方法として、静的setメソッドの導入が挙げられる。このメソッドを呼び出してテストごとに毎回新しいオブジェクトを生成できるため、実質的にインスタンス化できる。 また、他のグローバル変数を作らせないために、クラスがprivate化されていることがある。 本来、クラスのインスタンスは1つにすべきである。2つ以上作ると多くのリソース消費や、重大な問題につながりやすい。こうした概念を持った、グローバル変数を用いたデザインパターンのことを、Singletonと言う。 グローバル変数として利用するためにわざわざSingletonの性質を保つ理由はない。 サブクラスとメソッドのオーバーライドを利用して、擬装Singletonを作ることが可能である。 依存関係が広範囲の場合は、コンパイラまかせの変更を行うこともできる。 これらのリファクタリング全体が静的setメソッドの導入である。 なお、依存関係を排除したわけではない。排除を行うには、メソッドのパラメータ化やコンストラクタのパラメータ化を行う必要がある。 ### 9.5 恐るべきincludeの依存関係 他のクラスを知る際に、Javaではinportを用いる。コンパイラはコンパイル済みかを確認した上で、**未コンパイル分のみを新たにコンパイルする最適化機能**を備えている。 しかし、C++で用いられるincludeは、最適化機能が無く、全てのincludeを実行するようになっている。この結果、includeした先のクラスのincludeを読み込む連鎖が起こりかねず、処理時間が膨大になってしまうこともある。 こうした中でテストを行うのは至難だが、最善の方針は、includeを一度に一つだけ追加して、特定の依存関係の必要有無を判断することである。 擬装オブジェクトを生成して、その中でテストを行う。 ### 9.6 玉ねぎパラメータ オブジェクトをコンストラクタで生成することは、時に困難を伴う。多くの場合。適切に設定された別のオブジェクトを渡す必要がある。これらが重なっていくと、玉ねぎのようなオブジェクトの入れ子が出来上がってしまう。 こうした場合のテストは、まず何をしたいのかを見極める必要がある。voidでパラメータから得るものがなければnullを渡すことで対応できる。 いくつかの振る舞いが必要なら、インターフェースの抽出→擬装オブジェクトの生成が効果的である。 ### 9.7 別名のパラメータ インターフェースを作っても、クラスとの関係がほとんど1対1になってしまう場合は、インターフェースの抽出は使えない。 こうした場合は、サブクラスとメソッドのオーバーライドを用いることで、依存関係を排除できる。 ## 第10章 このメソッドをテストハーネスで動かすことができません メソッドを動かすことができない原因として以下の4つが考えられる。 1. privateメソッド等、可視性に問題がある場合。 2. メソッド呼び出しに必要なパラメータの生成が困難である場合。 3. メソッドに副作用があり、実行が不可能である場合。 4. メソッドが使ういくつかのオブジェクトを事前に検出する必要がある場合。 ### 10.1 隠れたメソッド privateメソッドをテストできる簡単な方法としては、publicメソッドにしてしまうことである。 しかし、publicメソッドにしてしまうと、たいていの場合、他のクラスから呼び出せてしまうことで予期せぬ振る舞いを起こす可能性がある。 こうした場合は、インターフェースやの抽出や、親子関係で対象メソッドをテスト用に擬装してから、publicに変更して受け取れるようにする。 また、Javaではリフレクションの機能を利用することで、privateメソッドの呼び出しが可能である。 ### 10.2 言語の「便利な」機能 publicなコンストラクタが無く、かつfinalやsealedになっているため継承が不可能である場合は、**パラメータの適合**を行う。 また、メソッド内で必要な要素を生成できない場合は、**APIをラップ**して、1組のプロパティクラスを作成する。 継承やインターフェースの抽出により擬装オブジェクトを作る場合は、**コンパイラまかせ**で本番用とテスト用を分割すればよい。 ### 10.3 検出できない副作用 あるオブジェクトが別のオブジェクトを呼び出している場合、その結果を知ることは困難である。 こうした場合、**メソッドの抽出**を行い、**コマンドとクエリの分離**を行う。 その後、**インターフェースの抽出や、サブクラス化とメソッドのオーバーライド**等を実施することで、擬装クラスを作成できる。 ## 第11章 変更する必要がありますが、どのメソッドをテストすればよいのでしょうか。 ### 11.1 影響の調査 変更する前に、変更によりどのメソッドに影響が出るのかを調べる必要がある。 影響スケッチは、影響を及ぼすメソッドから、影響を受けるメソッドに対して→を書くことで、影響の関係性を図に示したものである。 十分に構造化されているならば、多くのメソッドは影響スケッチの構造が単純になる。こうした状況のコードは理解しやすく、保守しやすい。 ### 11.2 前方向の調査 構造化が不十分な場合、得られる結果(変更)がなぜそうなるのか(どんな影響を与えるのか)を説明することが困難な場合がある。 こうした場合は変更点を元にした**前方向の調査**が必要である。 仕様化テストの場合は、もしオブジェクトが正しく動作しなかったら何が変わるかを考えることになる。 影響スケッチを書く場合は、調査対象のクラスを利用するすべてのクラスを対象とする。また、スーパークラス、サブクラスがある場合、その影響についても考慮に入れる。 変更の影響が何かを把握したうえで、どこで影響を検出できるのかがわかれば、その中から選んでテストを書くことができる。 ### 11.3 影響の伝播 影響は、3つの方法で伝播する。 1. 呼び出しに側によって使われる戻り値。 2. パラメータとして渡されるオブジェクトの変更。 3. staticデータ及びグローバルデータの変更。 #### 手順 1. 変更対象のメソッドを特定する。 2. そのメソッドに戻り値があれば、その呼び出しコードを調べる。 3. そのメソッドが何らかの値を更新するかを調べる。更新する場合は、その値を使うメソッドと、さらにそのメソッドを使うメソッドを調べる。 4. インスタンス変数やメソッドの利用者になり得るスーパークラスとサブクラスについても確認する。 5. そのメソッドに渡されるパラメータを確認する。変更しようとしているコードが、パラメータや戻り値のオブジェクトを更新していないかを調べる。 6. 特定したメソッドのいずれかで、グローバル変数やstaticデータを更新していないかを調べる。 ### 11.4 影響調査のためのツール 私たちが持つ武器の中で最も重要なツールは、プログラミング言語に関する知識である。 どんな言語にも、影響の伝播を防ぐための「防火壁(firewall)」がある。 これを利用すれば、防火壁を超えて調べなくてよくなる。 防火壁の一つに、フィールドのprivate化がある。 ### 11.5 影響分析から学ぶこと 機会がある限り、コードの影響を分析してみることが大事である。 最も優れたコードには、「落とし穴」は多くない。いくつかの「規則」が取り入れられているためである。 こうした規則の発見には、他の部分への影響が大きくかかわっている。 多くは処理の前後関係に関するものであり、影響を狭めることでプログラミングは楽になっていく。 ### 11.6 影響スケッチの単純化 影響スケッチにより、重複する呼び出しを見つけてそれを修正することで、影響スケッチをより単純化できる。 依存関係の排除はカプセル化を壊すことが多い。カプセル化は重要であるがそれ自体が目的ではない。あくまでも理解するための手段である。 カプセル化を壊すと調査がより難しくなるが、より直接的にテストケースを利用できる。 ###### tags: `読書`