Tbpgr Blog

元エンジニア 人事 tbpgr(てぃーびー) のブログ

書籍 Refactoring to Patterns | Simplification | Compose Method

パンくず

書籍 Patterns to Patterns
Simplification
Compose Method

概要

Compose Methodのリファクタリングについて

使用する場面

雑然とした長い処理のメソッドなどがある場合

対応方法

同レベルの粒度でExtract Methodを適用する。
役割を表す分かりやすい名前をつけることで、コメントが無くても
メソッド名を読めば処理の内容が分かるようにする。

利点と欠点

利点

メソッドが何をしているか効果的に伝える
・良い命名をして処理を分割することでメソッドをシンプルに出来る

欠点

・小さなメソッドが多くなりすぎる
デバッグが難しくなる

補足

デバッグが難しくなる、とあるがデバッグは問題の発生時に行うものであり
問題箇所の特定という意味ではメソッドが小分けになっているほうが
原因を特定しやすいという利点がある。
また、修正時も狭いスコープに適用することになり影響範囲が狭まる。

手順

・小さく考える。1メソッドの処理は5から10行を目安にする
・重複を削除し、デッドコードを削除する
・コミュニケーション目的。名前に目的を込める
・単純にする
・粒度を同じにする

サンプル

特に意味のある仕様ではありませんが、幾つかの意味のある処理の塊に対して
手続き的に並べただけの処理を用意します。
リファクタリングにより、細かいメソッドやクラスに分割し
メソッド名を順に読むだけで処理の内容を把握出来るレベルまで落とし込みます。

リファクタリング
# encoding: Windows-31J

#・入力文字列がnilかチェックする
#・入力文字列が空文字かチェックする
#・入力文字列が100文字以上だったらエラーにする
#・カンマで配列に分割する
#・各要素に対して以下の処理を行う
#  ・文字数を取得する
#  ・文字数が奇数ならodd、偶数ならevenを文字列として追加する
#・配列の内容を標準出力する
#・oddとevenのどちらが多かったかを比較して返却(1奇数が多い、0同じ、-1偶数が多い)
class ComplexClass
  def complex_method(original)
    raise 'nil' if original.nil?
    raise 'empty' if original.empty?
    raise 'over' if original.length >= 100
    original_list = original.split(',')
    odd_counter = 0
    even_counter = 0
    original_list.each do |each_data|
      len = each_data.length
      each_data << (len.odd? ? "odd":"even")
      if len.odd?
        each_data << "odd"
        odd_counter = odd_counter.succ
      else
        each_data << "even"
        even_counter = even_counter.succ
      end
    end
    return odd_counter <=> even_counter
    
    # debug code
    p original_list
  end
end

complex = ComplexClass.new
#complex.complex_method nil # => nil error
#complex.complex_method empty # => empty error
#complex.complex_method "0123456789"*10 # => over error
p complex.complex_method "1,12,123,1234,12345"
p complex.complex_method "1,12,123,1234"
p complex.complex_method "1,12,123,1234,12"
リファクタリング
# encoding: Windows-31J

#・入力文字列がnilかチェックする
#・入力文字列が空文字かチェックする
#・入力文字列が100文字以上だったらエラーにする
#・カンマで配列に分割する
#・各要素に対して以下の処理を行う
#  ・文字数を取得する
#  ・文字数が奇数ならodd、偶数ならevenを文字列として追加する
#・配列の内容を標準出力する
#・oddとevenのどちらが多かったかを真偽値で返却する
#・oddとevenのどちらが多かったかを比較して返却(1奇数が多い、0同じ、-1偶数が多い)
class ComplexClass
  def complex_method(original)
    check_original(original)
    original_list = get_original_list(original)
    original_list.each {|each_data|each_data << original_list.get_odd_even(each_data)}
    return original_list.compare_odd_even
  end
  
  def check_original(original)
    raise 'nil' if original.nil?
    raise 'empty' if original.empty?
    raise 'over' if original.length >= 100
  end
  
  def get_original_list(original);OriginalList.new(original.split(','));end
end

class OriginalList
  attr_accessor:original_list,:odd_count,:even_count
  
  def initialize(list)
    @odd_count=0
    @even_count=0
    @original_list = list
  end
  def increment_odd;@odd_count = @odd_count.succ;end
  def increment_even;@even_count = @even_count.succ;end
  
  # イテレーターの実装
  def each;@original_list.each{|value|yield(value)};end
  
  # 奇数と偶数の数を比較
  def compare_odd_even;return @odd_count <=> @even_count;end
  
  # 奇数偶数をカウントして奇数なら「odd」、偶数なら「even」を返却
  def get_odd_even(original)
      if original.length.odd?
        increment_odd
        return "odd"
      else
        increment_even
        return "even"
      end
  end
end


complex = ComplexClass.new
#complex.complex_method nil # => nil error
#complex.complex_method empty # => empty error
#complex.complex_method "0123456789"*10 # => over error
p complex.complex_method "1,12,123,1234,12345"
p complex.complex_method "1,12,123,1234"
p complex.complex_method "1,12,123,1234,12"
出力内容(共通)
1
0
-1