リファクタリング

「クラスを抽出」というリファクタリングがあるとしたらどういうものだろうか

あるとしたらというか、実際あるのですが(レガシーコード改善ガイドでは"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からしか参照されないもの}

実装

  1. コードベースに新たなインターフェイスRを加える。
   interface R {
      $(D1)
      $(V1)
    }
  1. コードベースに新たなクラスSを加える。
    class S {
      private R r;

      $(V2)

      public S(R r) {
         this.r = r;
      }

      $(rへの移譲でD1を実装)
      $(rへの移譲でV1を実装)
      $(M)
      $(D2)
    }
  1. Hを変更する。
  • 宣言に implements R を加える。
  • 変数 private S s = new S(this); を加える。
  • HにあるもとのMを、return s.M();のように移譲の1行で置き換える。

後片付け

  • HにあるD2を削除する。
  • Sが整理できたら、HのコンストラクタでSを外から受け取るようにする。

以上、どのステップでもコンパイルは通るはず。

できあがるSはたいていよくわからない物体だが、機嫌を伺う対象がRだけなので分割はやりやすくなる。