リファクタリングの原理原則

「コンピュータが理解できるコードは誰でも書ける。優れたプログラマが書くのは、人間が理解できるコードだ」

リファクタリングはコードの保守をする中でとっても大切なことは分かっているけど、原理原則を持っていない。原理原則が欲しいので、書籍「リファクタリング:Rubyエディション」を読んで、原理原則を手に入れようとしている。まだ読んでる途中だけど、アウトプット。書いてあるコードを丸写しするのではなく、自分なりにコードを書き直す事で理解した。

原理原則の前に大切なこと

リファクタリングでは、テストが命綱になる。リファクタリングが成功するかどうかは、よいテストが用意できているかどうかによって左右される。テストは、後でプログラムを書き換えなければならなくなった時の安全弁になるので、十分に時間をかけて作って良い。そして、テストは自己診断テストでなければならない。リファクタリングでは、プログラムを少しずつ変更すること。そうすれば、間違えても簡単にバグを見つけられる。

メソッドの抽出(Extract Method)(P31)

新しいメソッドにスコープが限られている変数、つまりローカル変数と引数になるべきものを元のコードから探す。たとえば、human と total がそれに当たるとしよう。そのうち、human はコード内で変更されないが、total は変更される。変更されない変数は引数として渡せるが、変更される変数はもう少し慎重に扱わなければならない。そういう変数が1つだけなら、戻り値として返せる。一時変数 total は、ループ開始時に 0 に初期化され、 case文に入るまで変更されないので、新メソッドの戻り値を代入出来る。

リファクタリング前
class Keiri
  def initialize                                                                                                                  
    @human = ["adult", "children"]
  end

  def fare_total
    result = "運賃合計: "
    total = 0

    @human.each do | human |
      case human
      when "adult"
        total += 200
      when "children"
        total += 100
      end
    end

    # 結果を出力
    result + "#{total}"
  end
end

keiri = Keiri.new
puts keiri.fare_total
リファクタリング後
class Keiri
  def initialize                                                                                                                  
    @human = ["adult", "children"]
  end

  def fare_total
    result = "運賃合計: "
    total = 0

    @human.each do | human |
      total += fare(human)
    end

    # 運賃合計を出力
    result + "#{total}"
  end

  # 運賃
  def fare(human_kind)
    case human_kind
    when "adult"
      result = 200
    when "children"
      result = 100
    end

    result
  end
end

keiri = Keiri.new
puts keiri.fare_total

メソッドの移動(Move Method)(P36)

メソッドの抽出の続き。クラス Keiri は fare メソッドを持っているが、実は運賃には複数種類があることが発覚。そのため、クラス Fare を作成して、fareメソッドを移動することにした。

リファクタリング前
class Keiri
  def initialize                                                                                                                  
    @human = ["adult", "children"]
  end

  def fare_total
    result = "運賃合計: "
    total = 0

    @human.each do | human |
      total += fare(human)
    end

    # 運賃合計を出力
    result + "#{total}"
  end

  # 運賃
  def fare(human_kind)
    case human_kind
    when "adult"
      result = 200
    when "children"
      result = 100
    end

    result
  end
end

keiri = Keiri.new
puts keiri.fare_total
リファクタリング後
class Keiri
  def initialize                                                                                                                  
    @human = ["adult", "children"]
  end

  def bus_fare_total
    result = "運賃合計: "
    total = 0

    @human.each do | human |
      total += Fare.bus(human)
    end

    # 運賃合計を出力
    result + "#{total}"
  end
end

class Fare
  # バス運賃
  def self.bus(human_kind)
    case human_kind
    when "adult"
      result = 200
    when "children"
      result = 100
    end

    result
  end

  #高速バス運賃
  def self.expressway_bus(human_kind)
    case human_kind
    when "adult"
      result = 3000
    when "children"
      result = 1500
    end

    result
  end
end

keiri = Keiri.new
puts keiri.bus_fare_total

一時変数から問い合わせメソッドへ(Replace Temp with Query)(P41)

以下は、極端な例だが・・・言いたいことは同じはず。

リファクタリング前
def hoge
  total = 0

  @human.each do |human|
    total = human.fare
    total += total
  end

  puts total
end
リファクタリング後
def hoge
   total = 0

  @human.each do |human|
    total += human.fare
  end

  puts total
end

ループからコレクションクロージャメソッドへ(Replace Loop with Collection Closure Method)(P49)

リファクタリング前
class Hoge
  def total
    result = 0

    [1, 2, 3, 4, 5].each do |n|
      result += n
    end

  result
end
リファクタリング後
class Hoge
  def total
    [1, 2, 3, 4, 5].inject(0) { |total, n| total + n }
  end
end

書籍紹介

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

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