Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

書籍 リファクタリング−プログラマーの体質改善 | メソッドの構成 | メソッドオブジェクトによるメソッドの置き換え

内容

リファクタリング

メソッドオブジェクトによるメソッドの置き換え

適用ケース要約

長いメソッドが「メソッドの抽出」を適用できないようなローカル変数の使い方をしている。

適用内容要約

メソッド自身をオブジェクトとし、すべてのローカル変数をそのオブジェクトのフィールドとする。
そうすれば、そのメソッドを同じオブジェクトの中のメソッド群に分解できる。

適用詳細

メソッドを短くすることは可読性を高めます。
多くのローカル変数があり、他のリファクタリングを適用しにくい場合などに
メソッドオブジェクトを利用するのが有効です。

サンプル

RubyQuestというRPGを実装します。
攻撃は力、武器、乱数で構成されます。
攻撃のダメージは敵の防御で緩和されます。
攻撃は一定確率でミスします。
攻撃は一定確率でクリティカルヒットして大ダメージを与えます。

サンプルコード

リファクタリング

class RubyQuest
  def attack(power,weapon,enemy_deffence)
    critical_magnification = 1.5
    
    if (rand(9)==0)
      puts "攻撃をミスした!"
      return
    end
    
    addtional_dmg = rand(10)
    total_dmg = power + weapon + addtional_dmg - enemy_deffence
    total_dmg = 0 if total_dmg < 0
    if (rand(4)==0)
      print "クリティカルヒット!!"
      total_dmg*=critical_magnification
    end
    puts "#{total_dmg.to_i}のダメージを与えた"
  end
end

power = 50
weapon = 40
enemy_deffence = 91

(1..10).each {|count|
  RubyQuest.new.attack power,weapon,enemy_deffence
}

リファクタリング

class RubyQuest
  def attack(power,weapon,enemy_deffence)
    attack_calculator = AttackCalculator.new(power,weapon,enemy_deffence)
    attack_calculator.attack
  end
end

class AttackCalculator
  attr_accessor :power,:weapon,:enemy_deffence
  CRITICAL_MAGNIFICATION=1.5
  RANDOM_DAMAGE_MAX=10
  # ミス率 1/10
  ATTACK_MISS_PROBABILITY=10
  # クリティカル率 1/5
  CRITICAL_ATTACK_PROBABILITY=5

  def initialize(power,weapon,enemy_deffence)
    @power=power
    @weapon=weapon
    @enemy_deffence=enemy_deffence
  end
  
  def attack()
    if (is_miss_attack)
      puts "攻撃をミスした!"
      return
    end
    
    total_damage = get_total_dmg
    if (is_critical_attack)
      print "クリティカルヒット!!"
      total_damage*=CRITICAL_MAGNIFICATION
    end
    puts "#{total_damage.to_i}のダメージを与えた"
  end
  
  private
  def is_miss_attack()
    return get_random_probability(ATTACK_MISS_PROBABILITY)
  end
  
  private
  def is_critical_attack()
    return get_random_probability(CRITICAL_ATTACK_PROBABILITY)
  end
  
  private
  def get_random_probability(probability)
    return rand(probability-1)==0
  end
  
  private
  def get_addtional_damage()
    return rand(RANDOM_DAMAGE_MAX)
  end
  
  private
  def get_total_dmg()
    total_dmg = @power + @weapon + get_addtional_damage - @enemy_deffence
    total_dmg = 0 if total_dmg < 0
    return total_dmg
  end
end

power = 50
weapon = 40
enemy_deffence = 91

(1..10).each {|count|
  RubyQuest.new.attack power,weapon,enemy_deffence
}

▼出力結果(共通)

クリティカルヒット!!7のダメージを与えた
クリティカルヒット!!12のダメージを与えた
攻撃をミスした!
3のダメージを与えた
8のダメージを与えた
5のダメージを与えた
4のダメージを与えた
4のダメージを与えた
クリティカルヒット!!1のダメージを与えた
クリティカルヒット!!1のダメージを与えた
解説

リファクタリング前は様々な計算式が混在した長いメソッドが定義されていて可読性が低いです。
リファクタリングにより、メソッドメソッドオブジェクに変更します。
これにより、リファクタリングが進み非常にすっきりとした構造になりました。