読者です 読者をやめる 読者になる 読者になる

will and way

ただの自分用メモを人に伝える形式で書くことでわかりやすくまとめてるはずのブログ

レガシーコード改善ガイド6章〜10章

今回からケーススタディ

6. 時間がないのに変更しなければなりません
冒頭:
テストを書かずとも変更を加えるにはどうすべきか。
既存の振る舞いを変えずに変更するTipsを紹介する。

スプラウトメソッド
既存のコードに新たな仕様が追加
→必要箇所へ、ロジックを追加するのではなく、メソッドとして切り出して
そのメソッドはテスト可能にしておく。


既存コードへの変更はそのメソッドの呼出をするのみにする。

スプラウトクラス:
1.既存のクラスに変更を加えたいが、テストハーネス内でテスト可能でない場合に変更部分を切り出してクラス化する
2.既存のクラスに新たに責務が加わる場合に、別クラスとして実装し、呼び出すだけにする。


スプラウトhogehogeの重要な目的
→既存コードに直接ロジックを埋め込まないこと。直接編集する量を極力減らすこと。
→別の場所に埋め込むことで、単体テストをしやすくすること
メソッドの責務を重くしないこと


その反面、全体が複雑化するので、理解しづらくなる。

ラップメソッド
1.同じタイミングで処理しなければならないための処理を別メソッドに切り出(ラップ)して本流から呼ぶ。
void hoge() {

処理A
処理B
}


から


void hogeA() {
処理A
}


void hogeB() {
処理B
}


void hoge() {
hogeA();
hogeB()
}


→処理Cを入れたくなったらhogeC()を作ればOK!メソッドがかるくなるね


2.複数の元からあったメソッドや、まとめたく成ったメソッドを呼び出すだけのメソッドを用意し、必要なところから呼ぶ
void hoge() {
処理A
処理C
}


void huga() {
処理B
}


から


//hoge, hugaをラップするメソッド
void hogehuga() {
hoge();
huga();
}

ラップクラス(Decoratorパターン)
1、処理を追加したいクラスAをインタフェース化する

2、内部的にインタフェースをもつ実クラスをつくる(ラップクラス)
コンストラクタで抽入する
3、そのクラスの呼出に追加したい処理をprivateメソッドで持つ(メソッドP)
4、抽入したクラスにデリゲーションし、前後でメソッドPを呼び出す。

inputstream-> デコレータパターン

7. いつまでたっても変更作業が終わりません。
遅延時間
テストにかかる時間が長い=テストする度に遅延が発生する
テストに時間がかかる原因は主に依存関係
クラスAを変更すると、Aに依存しているクラス全てが再コンパイルが必要になる
インターフェースCの場合、Cを実装しているBに変更があっても、Cに依存しているクラスに再コンパイルの必要性がなくなる。

依存関係の排除
インタフェース化して依存関係を排除すると、
→依存関係を減らすことで、コンパイルの箇所を減らすことがデキる。

依存関係の排除の為にインターフェースやクラスが増えるとフルビルドの時間が増えるが、
差分ビルドでは減る。

結論:依存部分を減らし、ビルド時間を減らす

Intellijでは設定次第。

8. どうやって機能を追加すればよいのでしょうか
TDDをしよう
1,失敗するテストケースを記述する
2,コンパイルできるようにする
3,テストを通す(既存コードへの変更はなるべく加えない)
4,重複を取り除く(ハードコーディングを無くしたり、重複している処理をメソッドに切り出したり。)
5,繰り返す

差分プログラミング
→Abstractにして共通処理をもち、必要に応じて処理を実装する。
上階層で実装されている部分はオーバーライドしない。

Liskovの定理に気をつける。
結論:TDDをしよう

9. このクラスをテストハーネスに入れることができません。
テストハーネスに入れられない主な原因4つ


* クラスのオブジェクトを簡単に生成できない
* そのクラスを含むテストハーネスを簡単にビルドできない
* 使用しなければならないコンストラクタが複雑
* かなりの処理がコンストラクタ内で行われ、その内容を検出する必要がある

まずはオブジェクトを作ってみる
new して、コンパイラに怒られてみる→オブジェクトの生成に必要な物がわかる。
必要なものが生成しづらい場合、またはユニットテストの原則に添えない場合、インターフェース抽出をして、擬装オブジェクトをつくるといい。

nullを渡すようなコードは汚い→呼び出し先でnullで分岐させてるケースが有る
呼び出し元と先で挙動を知っている必要があり、メソッド自体の関係性も密になる。

Nullオブジェクトパターン
何もしないオブジェクトを渡すことに依って、nullによる分岐をさせる必要がなくなる。
nullprofilerとかまさに。

コンストラクタに入れられない場合。
コンストラクタが思い場合、全てをパラメータ化するのは手間。


インスタンスのすり替えメソッドを準備してモッキングする。

シングルトンパターンはテストしづらいケースが考えられる。
そんなときはstaticなセットメソッドをもうける。シングルトン制約はゆるくなってしまうが、よしとする。

シングルトンを用いている理由がリソースやロジックに致命的でない場合、
規制を緩和しても構わない。
サブクラスを使えばpublicにせずともprotectedですむ。
そもそも論として、グローバル変数が散らばっていたり、
パラメータ化しづらい場合、クラスの責務が大きすぎるので設計がクソ

また、インタフェース化で依存関係が排除できない場合、そのクラスのサブクラスを作り、想定する動きをするようにオーバーライドするのがいい。いわゆるモッキング。

結論:実際に動作させたくないクラス、そのクラスのテスト軸から離れるクラスはモッキングしてするなりして、ロジックだけを動かせるようにしよう。
テストのために公開レベルを上げるのはアリ。


10. このメソッドをテストハーネスで動かすことが出来ません。

隠れたメソッド
Privateメソッドの改修を入れた時
1publicメソッドからのテストを検討する
ロジックの一環したてすとができる
それでもテストしたい➡︎テストしたいメソッドはpublicで有るべき。
公開すると悪影響→protectedでサブクラスを作りテストする。
カプセルに反するが、テストの保護をうけたいなら仕方なし。

パッケージプライベートに、しておくとよし!

言語の便利な機能
finalがついていてサブクラス化してもテストできない
→引数を親クラスにする。テストの時に渡すクラスを変える

呼び出し先で依存性のあるメソッドを読んでる→インタフェースを抽出してラッパーをかく。
インタフェースがあるので擬装クラスもつくれる。



検出出来ない副作用
1メソッド複数の処理を同時にしすぎている
様々なオブジェクトをメソッドの引数に渡さなければ処理ができない時、メンバ変数にくくり出す。
そして、メソッドを分割する。
→サブクラス化してオーバーライドできる。

結論:親クラスやインタフェース化でコンパイラ任せテクによって、サブクラス化できないクラスをモッキングすることができる。
まさにオブジェクト接合部。ただし、本番では正しいオブジェクトが入らないリスクはあるが、トレードオフ