レガシーコード改善ガイド
https://gyazo.com/5de9f3af59aa58038e6a8fed1b3b71a3
リファクタリングの基本として、変更前と変更後で振る舞いが変わらないことを確認するためのテストを書き、検証しながら少しずつ作業を進めることでソフトウェアの保守性を向上できるという考え方があります
振る舞いを保つのは、非常に困難
どんな変更を行われなければならないか
変更が正しく行われたことをどうすれば確認できるか
何も壊してなことをどうすれば確認できるか
テストがない場合には、テストがない中でできる一番安全で保守的な作業のみをする
"Edit and Pray" vs "Cover and Modify"
注意をより多く払ったからといって、必ずしもそれに比例して安全性が向上するとは限らない
テストで保護することが重要
テストハーネス
テストを実行するためのクラスの集合空間
テストハーネスに入れることができればテストが可能になるという考え
レガシーコードの変更手順
1. 変更点を洗い出す
2. テストを書く場所を見つける
3. 依存関係を排除する
4. テストを書く
5. 変更とリファクタリングを行う
オブジェクト接合部
あるオブジェクトが別のオブジェクトを参照している箇所 (メソッドなどの呼び出しも含む)
ラップしてテストを書く
スプラウトメソッド
ある処理を追加するときには、それを別のメソッドとして追加してそのテストだけは書くこと
スプラウトクラス
スプラウトメソッドのクラス版
ラップメソッド
ある処理をまとめて別のメソッドを入り口にして呼び出すようにする
ラップクラス
ある処理を単純にラップするクラス
具体への依存をなくす
最初のステップは、 テストハーネスで必要なクラスのインスタンス化を試みること
変更対象のクラスをテストハーネスに入れることができると以下のサイクルの時間が短くなる
変更
コンパイル
リンク
テスト
Interfaceに依存させるようにすることでコードにファイアウォールを置いてるイメージ
テスト可能性も高くなる
コンパイル時間も短くなる
Privateメソッドのテスト
publicメソッド経由でテストできるならそうする
privateメソッドをテストしたいときには、そのクラスの構造がいろんなことをやりすぎてしまってることが多い
影響の調査
影響スケッチ
影響範囲をメソッド呼び出しなどともにラフに線でつないだもの
https://gyazo.com/11d320b521e493cbd59cd6774530e606
この図が単純なほど保守が楽
割り込み点 (Interception Point)
ある変更に対して影響のあるものを割り込み点とよぶ
上の図でいえばどれも割り込み点になる
割り込み点をテストすることでその前後での動作の保証が可能になる
絞り込み点 (Pinch Point)
fire wall のようにあるクラス群が孤立するような点
上記で言えば、 BillingStatement.makeStatment が絞り込み点になる
ここを仕様化テストできれば、その後ろの振る舞いをテストできることになるので、保護の範囲が広がる
ただし多くの場合には完全な絞り込み点を見つけるのは困難
絞り込み点を作るように設計するとそこから先は疎結合になり、保守性も高くなる
影響範囲は、基本的に以下によって伝搬する
1. 呼び出し側によって使われる戻り値
2. パラメータとして渡されるオブジェクトの変更
3. staticデータ、あるいはグローバルなデータの変更
ほぼ全てのレガシーシステムでは、「システムがどのように動くべきか」よりも、「実際にどうのように動いてるか」の方が重要です。
システムが稼働しているなら、自分の目にはバグに見えたとしても誰かがその振る舞いに依存していないかどうかを調べる必要がある。
ライブラリなどについて
導入することで、プロジェクトの開発期間を大幅に短縮できる可能性がある
唯一の問題はいとも簡単にライブラリに強く依存した状態になってしまうこと
対策は
薄いラッパーを作って、APIをラップする
責務をもとに抽出する
試行リファクタリング
コードを理解するためにリファクタリングを行うこと
実際に本番にcommitされることはないがブランチをチェックアウトして好きにリファクタリングをしてコードの理解を目指す手法
アーキテクトがいるのは素晴らしいことですが、アーキテクチャを保つための鍵は、チームの全員がアーキテクチャとは何であるかを知り、アーキテクチャに関心を持つことです。コードに関係する全ての人は、アーキテクチャを知るべきですし、アーキテクチャに習熟している人からの恩恵を受けるべきです。
大きなクラス
問題点
理解容易性が低くなる
コンフリクトが発生する確率が増えるので、作業計画の調整が難しくなる
テストを書く労力が大きい
変更が必要な場合には、新しい小さなクラスかメソッドとして切りだすといい
既存のメソッドについては、メソッドの呼び出しをマッピングして、絞り込み点を探して分離するを繰り返していく
大きなクラスに依存しているメソッドがある場合には、インターフェースを少しづつ分離していくといい
ある処理群をクラスに切り出す方法もある
移動時には、オーバーライドなどの影響を最小にするために MOVING_ という接頭語をつけると安全
コンパイラに任せる (Lean on the Compiler)
意図的にコンパイルが落ちる状態にして、コンパイルが通ればリファクタリングが完了するように計画を立てる
例えばメソッドの移動時に前のメソッドは消してしまってエラーが出てるところの呼び出しを置き替えればいいように。
メソッドオブジェクトの取り出し (Break Out Method Object)
あるクラスからメソッド群をクラスとして切り出す方法
メソッドを移動するクラスを作って、なるべくコピペに頼って保守的に移動する
最終的には、インターフェースを切り出して、もともとのクラスはインターフェースに依存できるようになると依存度が下がる
インスタンス委譲の導入 (Introduce Instance Delegator)
Static メソッドはテスト要の処理に切り替えることが難しい
code:java
public class BankingServices {
public static void updateAccountBalance(int userID, Money amount) {}
}
public class SomeClass {
public void someMethod() {
BankingService.updateAccountBalance(id, sum)
}
}
//////////////
public class BankingServices {
public static void updateAccountBalance(int userID, Money amount) {}
public void updateBalance(int userID, Money amount) {
updateAccountBalance(userID, amount)
}
}
public class SomeClass {
public void someMethod(BakingServices services) {
services.updateAccountBalance(id, sum)
}
こうすることで徐々にStaticメソッドを排除していける
また、BankingServices は実装を抽出してインターフェース化することもできる
コンストラクタのパラメータ化 (Parameterize Constructor)
code:java
public class MailChecker {
public MailChecker(int checkPeriodSeconds) {
this.receiver = new MailReceiver(); // を追加
this.checkPeriodSeconds = checkPeriodSeconds;
}
public MailChecker(MailReceiver receiver, int checkPeriodSeconds) {
this.receiver = receiver;
this.checkPeriodSeconds = checkPeriodSeconds;
}
}
既存の呼び出しを変更することなく、新しいことができる
コンストラクタで生成するようになって副作用や依存関係を導入してしまってるがリファクタリングの手法としては、保守的かつ簡単
感想
レガシーシステムを改善する方法とあるが、この本自体はテストとリファクタリングのテクニッックの本になっている
その上でテストとリファクタリングの本としては、かなりまとまっていて我々がなぜリファクタリングするかということをうまく説明している
自分がよくやっていた手法が名前つきでカバーされてるのを見て自分のセンスに安心できた