なんとかするから、なんとかなる

エンジニア関係のことを書きます

レガシーコード改善を読んで その2

本投稿の話題

既存コードを変更する際の技。今後、テストを導入できるようにするための布石となる方法。 Swiftに適応するならどうするか。

スプラウトメソッド

  • 既存のメソッド内に、新たなメソッドを呼び出すように変更を加える方法。
  • 新たなメソッド内だけでもテストが可能となる。

例えば、以下のようなコードがあったとする。

func example() {
   // doSomething 何かしらの処理を実行している
}

doSomethingの後に、何かしらの新たな処理を実行したい場合

func example() {
   // doSomething
   // example()内で処理を書くことも可能だが、以下のようにdoSomething2を別に用意する。
   doSomething2()
}

func doSomething2() {
   // 新たな処理をここに書く
}

上記のようにすることで、なるべく既存のdoSomethingに変更を加えずに、修正をすることができる。

また、もしdoSomethingが所属するクラスの依存関係が複雑すぎる場合でも、doSomething2の処理に必要な情報を引数とすることで、doSomething2だけでもテストが可能となる。(新規追加処理がテストできる)

スプライトクラス

  • 既存クラスに対して新たな責務を追加したい場合に有効
  • 新たな責務を別のクラスとして切り分けて置く
  • 変更が必要な既存処理で新たなクラスのインスタンスを生成/利用する。

コードが複雑になるため、使い過ぎには注意が必要となる。一方で別クラスに分けることで、新たな責務を担うクラスだけでもテストが可能となる。

ラップメソッド

  • 既存のメソッドをラップするメソッドを用意し、既存処理→新規処理(新規処理→既存処理)と呼び出す
  • 既存のメソッドの処理を一切触れることなく、新たな処理を追加することができる
  • 既存のメソッドの前・後処理として、処理が独立しているときに有効

例えば、以下のようなコードがあったとする。

func exmaple() {
   // doSomething
}

exmapleを呼び出している箇所すべてに対して、後処理(doSomething3)を追加したい場合

func exmaple(){
   doSomething2()
   doSomething3()
}

func doSomething2() {
   // doSomething
}

func doSomething3() {
   // 新たな処理
}

既存の処理をdoSomething2として再定義し、exmaple()では既存の処理と新たな処理(doSomething3)を呼びだす。

上記のようにすることで、既存の処理を変更せずに新たな処理を追加することができる。

新たな処理を一部だけ追加したい場合は、exmapleはそのままで、exmaple2を新たに定義し、exampleとdoSomething3を呼び出す。新たな処理を追加したい箇所だけexample2に変更すればよい。

ラップクラス

既存のクラスのメソッドをProtocolで抽出し、新たに定義するクラスをProtocolに適合させる。 新たなクラスのインスタンス生成時に、既存クラスのインスタンスを渡す。変更が必要な個所だけ修正する。 変更が必要ない箇所は既存クラスのインスタンスに処理を委譲する。

Protocolに抽出しなくても、既存クラスが継承可能ならば継承でも実現できる。

protocol SomeProtocol {
    func something1()
    func something2()
}

class SomeClass: SomeProtocol {
    func something1() {
        
    }
    
    func something2() {
        
    }
}

class SomeClassWrap: SomeProtocol {
    let someProtocol: SomeProtocol
    
    init(with something: SomeProtocol) {
        self.someProtocol = something
    }
    
    func something1() {
        self.someProtocol.something1()
    }
    
    func something2() {
        self.someProtocol.something2()
    }
}

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)