あるとしたらというか、実際あるのですが(レガシーコード改善ガイドでは"Sprout Class"と呼ばれる)自動化されたものをまだ見たことがないので、ときどき手動でする作業を文章にしてみます。
使いどころ
例A
- Hというクラスが巨大でテストしづらい。
- Hの主機能とはいえないMというメソッドがある。(Mは複数でもよい)
- Mは切り取り&貼り付けで他に切り出せない程度にはHに根を張っている。
- さしあたりH全体のテストは諦めて、Mだけテスト可能にしたい。
例B
- Hというクラスが便利だが巨大で触りたくない。
- HにMというメソッドがある。(Mは複数でもよい)
- 基本的にはHの役割だが、Mだけちょっと違う処理をしたい状況にある。
- 安直にやるとフラグを足すなりHをコピーして書き換えて多態性にするなりというところだが、それを繰り返した結果今のHがあり、限界が近い。
概要
方法
- 新たにクラスSを作り、メソッドMをそちらに引っ越す。(Mは複数でもよい)
- HのMはSへの移譲にして残すことで機能を保つ。
- Mが依存するメソッドがあれば、できるだけそれもSに引っ越す。
- 引っ越しきれないメソッドはSからHを呼び出すことで既存の動作を保つ。
- (optional)相互依存をごまかすため、SからHを呼ぶとき、Sが呼び出すMのメソッドをまとめたインターフェイスIを用意し、それを通して呼び出す。
操作による効果
- リファクタリングの前提として、Hの外部から見た振る舞いは変化しない。
- Sはほぼテストできる状態になる。
- Mと一緒に引っ越すメソッドの量に応じて、Hがダイエットされる。(逆に、この効果の大きさがMを選ぶ基準になる)
作業
下準備
まず、クラス内のメソッドと変数の依存関係を調べて、以下をリストアップする。
-
D = {Mが直接的・間接的に呼び出すH中のメソッド}
-
D1 = {Dのうち、M以外のメソッドから参照されるもの}
-
D2 = {Dのうち、M以外のメソッドから参照されないもの}
-
V = {Mが利用するインスタンス変数} ∪ {D2が利用するインスタンス変数}
-
V1 = {Vのうち、MかD2以外のメソッドから参照されるもの}
-
V2 = {Vのうち、MやD2からしか参照されないもの}
実装
- コードベースに新たなインターフェイスRを加える。
interface R {
$(D1)
$(V1)
}
- コードベースに新たなクラスSを加える。
class S {
private R r;
$(V2)
public S(R r) {
this.r = r;
}
$(rへの移譲でD1を実装)
$(rへの移譲でV1を実装)
$(M)
$(D2)
}
- Hを変更する。
- 宣言に
implements R
を加える。 - 変数
private S s = new S(this);
を加える。 - HにあるもとのMを、
return s.M();
のように移譲の1行で置き換える。
後片付け
- HにあるD2を削除する。
- Sが整理できたら、HのコンストラクタでSを外から受け取るようにする。
以上、どのステップでもコンパイルは通るはず。
できあがるSはたいていよくわからない物体だが、機嫌を伺う対象がRだけなので分割はやりやすくなる。