リファクタリング: Ruby エディション 第11章

## はじめに

<img src='https://qiita-image-store.s3.amazonaws.com/0/186009/5d219e32-cd26-4cbd-1390-ad61e884ade0.jpeg' height=300px >

Amazon: https://www.amazon.co.jp/dp/4048678841

第6章から第11章まではリファクタリングの具体的なテクニックがまとめられています。
今回は `リファクタリング: Rubyエディション` の `第11章 一般化の処理` で紹介されているいくつかのテクニックについて、まとめました。

※ 以下のサンプルコードは書籍で紹介されているコードを参考に、自分で作成したコードを載せています。（著作権を侵害しないように）

## テクニック一覧

- [Pull Up Method](https://qiita.com/items/aeee895399655b9c701b#pull-up-method)
- [Push Down Method](https://qiita.com/items/aeee895399655b9c701b#push-down-method)
- [Extract Module](https://qiita.com/items/aeee895399655b9c701b#extract-module)
- [Inline Module](https://qiita.com/items/aeee895399655b9c701b#inline-module)
- [Extract Subclass](https://qiita.com/items/aeee895399655b9c701b#extract-subclass)
- [Introduce Inheritance](https://qiita.com/items/aeee895399655b9c701b#introduce-inheritance)
- [Collapse Hierarchy](https://qiita.com/items/aeee895399655b9c701b#collapse-hierarchy)


## テクニック詳細

### Pull Up Method

どのサブクラスでも同じ結果になるメソッドがあるときに、メソッドをスーパークラスに移すテクニック

```ruby:サンプル

# リファクタリング前
class Room
end

class SingleRoom < Room
  def base_price(date)
    date * 3000
  end
end

class SweetRoom < Room
  def base_price(date)
    date * 3000
  end
end

# リファクタリング後
class Room
  def base_price(date)
    date * 3000
  end
end

class SingleRoom < Room
end

class SweetRoom < Room
end
```

重複するメソッドがあると、片方を修正して、もう片方を修正し忘れるというリスクを抱えることになります。つまり、将来のバグの温床になる可能性が少なくありません。

同じクラスを継承するサブクラスに、同じ処理を実行するメソッドがあるときは、メソッドをスーパークラスに移動することで重複を取り除き、バグを産むリスクを低くしましょう。

### Push Down Method

スーパークラスのメソッドが一部のサブクラスでしか使われていないとき、メソッドをサブクラスに移すテクニック

```ruby:サンプル

# リファクタリング前
class Employee
  def bonus
    monthly_salary
  end
end

class PartTimeEmployee < Employee
end

class FullTimeEmployee < Employee
end

# リファクタリング後
class Employee
end

class PartTimeEmployee < Employee
end

class FullTimeEmployee < Employee
  def bonus
    monthly_salary
  end
end
```

**Pull Up Method** とは逆のテクニックで、スーパークラスのメソッドが一部のサブクラスでしか使われていないときは、そのメソッドをサブクラスにさせます。

上記のサンプルだと、スーパークラスに bonus メソッドを定義していては、 `PartTimeEmployee(アルバイト)` に対しても、ボーナスを支給する会社だと誤解を与えてしまいそうですね。

### Extract Module

複数のクラスに重複するメソッドがあるときに、モジュールをインクルードするようにして、そのモジュールにメソッドを移すテクニック

```ruby:サンプル

# リファクタリング前
class Deposit
  attr_accessor :account_id

  def capture_account_id(account)
    self.account_id = account.id
  end
end

class Withdraw
  attr_accessor :account_id

  def capture_account_id(account)
    self.account_id = account.id
  end
end

# リファクタリング後
module AccountIdCapture
  def capture_account_id(account)
    self.account_id = account.id
  end
end

class Deposit
  include AccountIdCapture
  attr_accessor :account_id
end

class Withdraw
  include AccountIdCapture
  attr_accessor :account_id
end
```

**Pull Up Method** で先述しましたが、メソッドの重複は将来のバグの温床になりかねません。このテクニックも、そういった重複を取り除くためのテクニックです。

**Pull Up Method** とは違い、サブクラス・スーパークラスの関係に限らず、複数のクラスでメソッドが重複してしまっているときに、適用するとよいテクニックです。

ただ気をつけなければならないのは、モジュールはグループとして意味を持つものでないといけないということです。モジュールが特定の意味をもつグループではなくなると、ただメソッドをかき集めた入れ物になってしまい、モジュールが膨れ上がり、どこに何のメソッドがあるかわからなくなってしまいます。

これではリファクタリングの目的である、効率の良い開発ができなくなってしまいますね。

### Inline Module

モジュールがメソッドの重複を避ける役割を果たしていないときに、モジュールをインクルードしているクラスにメソッドを移すテクニック

```ruby:サンプル
# なし
```

これは **Extract Module** とは逆のテクニックになります。モジュールをインクルードするということは、メソッドを探すときに、クラスを見て、そこになければインクルードしている関連のありそうなモジュールを特定し、そのモジュールの中を見るという手間が発生してしまいます。

もしモジュールが重複を避けるという役割を果たしていなかったときは、手間が増えるだけになってしまいます。そういったときはモジュールをインクルードしているクラスに、モジュール内のメソッドを移して、効率良く開発できるようにしましょう。

### Extract Subclass

クラスが一部のインスタンスしか使わないフィールドをもっているときに、サブクラスを作ってリファクタリングするテクニック

```ruby:サンプル

# リファクタリング前
class JobItem
  attr_accessor :unit_price, :quantity, :is_development_tool, :engineer

  def initialize(unit_price, quantity, is_development_tool, engineer)
    @unit_price = unit_price
    @quantity = quantity
    @is_development_tool = is_development_tool
    @engineer = engineer
  end

  def unit_price
    is_development_tool? ? unit_price * @engineer.rate : @unit_price
  end

  def is_development_tool?; @is_development_tool end
end

# リファクタリング後
class JobItem
  attr_accessor :unit_price, :quantity

  def initialize(unit_price, quantity)
    @unit_price = unit_price
    @quantity = quantity
  end
end

class DevelopmentItem < JobItem
  attr_accessor :engineer

  def initialize(unit_price, quantity, engineer)
    super(unit_price, quantity)
    @engineer = engineer
  end

  def unit_price
    @unit_price * @engineer.rate
  end
end
```

このテクニックを適用するきっかけとしてもっとも大きいのは、クラスの一部のインスタンスしか使わないメソッドやフィールドがあるとわかったときです。

上記のサンプルは、開発用ツールに関してはエンジニアの階級による割引価格で購入できるという前提でコードを書いています。リファクタリング前のコードだと、 `is_development_tool` が　`true` のときだけ、 `engineer` の情報が必要になります。

`JobItem` クラスに開発用ツールときだけ使用される、フィールドやメソッドがあることになります。こういった場合に、 **Extract Subclass** を適用して、サブクラスを抽出すれば、コードを綺麗にすることができますね。

### Introduce Inheritance

似た機能を持つ2つ(複数)のクラスがあるときに、片方のクラスをスーパークラスにし、共通するフィールド・メソッドを移すテクニック

```ruby:サンプル

# リファクタリング前
class Room
  attr_accessor :visitor, :base_price, :days

  def initialize(visitor, base_price, days)
    @visitor = visitor
    @base_price = base_price
    @days = days
  end

  def visitor_name
    @visitor.name
  end

  def total_price
    @base_price * @days
  end
end

class SweetRoom
  attr_accessor :visitor, :base_price, :days, :room_service_fee

  def initialize(visitor, base_price, days, room_service_fee)
    @visitor = visitor
    @base_price = base_price
    @days = days
    @room_service_fee = room_service_fee
  end

  def visitor_name
    visitor.name
  end

  def total_price
    @base_price * @days + @room_service_fee
  end
end

# リファクタリング後
class Room
  attr_accessor :visitor, :base_price, :days

  def initialize(visitor, base_price, days)
    @visitor = visitor
    @base_price = base_price
    @days = days
  end

  def visitor_name
    @visitor.name
  end

  def total_price
    @base_price * @days
  end
end

class SweetRoom < Room
  attr_accessor :visitor, :base_price, :days, :room_service_fee

  def initialize(visitor, base_price, days, room_service_fee)
    super(visitor, base_price, days)
    @room_service_fee = room_service_fee
  end

  def total_price
    super + @room_service_fee
  end
end
```

このテクニックも重複を避けるためのテクニックです。何度もしつこいようですが、重複するコードがあると不具合が発生する可能性が高くなります。

似たような機能をもつ2つのクラスがあるときに **Introduce Inheritance** を適用すれば、コードを重複を避けることができます。メソッド名が同じで中身が若干違うメソッドも super メソッドを使うことでシンプルに書けるかもしれません。

### Collapse Hierarchy

スーパークラスとサブクラスに大差がないとき、両者を1つにするテクニック

```ruby:サンプル
# なし
```

開発を進み、気がつくとスーパークラスとサブクラスに大した差分が無くなっていたりすることがあるようです（私はまだ経験したことがありませんが。。）

それに気付いたときに放置するのではなく、両者を1つにしてしまいましょう。クラスが少なくなれば見る箇所も少なくなるので、効率よく開発ができるようなるはずです。


## 考察

11章では、コードの重複を避けるべきだという主張が何度もされています。重複したコードがあると、片方は修正したのに、もう一方を修正し忘れる可能性が高まり、不具合を発生する可能性を高めます。

また重複するコードがある箇所を修正するたびに、様々なファイルを探し回るという手間もかかってしまい、効率のよい開発の妨げとなってしまいます。

つまり、開発効率を向上するため、不具合を未然に防ぐために、重複しているコードをいかに一箇所にまとめるかが大切なのだと考えます。

## 関連

- [リファクタリング: Rubyエディション 第2章](https://qiita.com/tamago3keran/items/dbe43cfcad01b4bac3ce)
- [リファクタリング: Rubyエディション 第6章](https://qiita.com/tamago3keran/items/898936b716984cd55b1f)
- [リファクタリング: Rubyエディション 第7章](https://qiita.com/tamago3keran/items/d435f6c1280f8de29ad4)
- [リファクタリング: Rubyエディション 第8章](https://qiita.com/tamago3keran/items/2de394cfa1d36fdba924)
- [リファクタリング: Rubyエディション 第9章](https://qiita.com/tamago3keran/items/2818b20cdc42bb9c5388)
- [リファクタリング: Rubyエディション 第10章](https://qiita.com/tamago3keran/items/81a7fbd68d33c705b452)