LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

あるとしたらというか、実際あるのですが(レガシーコード改善ガイドでは"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だけなので分割はやりやすくなる。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1