Extract Method(メソッドの抽出)

所感

プロダクトコード

Introduce Explaining Variable(説明用変数の導入)と同様に、プロダクトコードが読みやすくなった。また、「base_price」のロジックを再利用できるようになった。

テストコード

Introduce Explaining Variable(説明用変数の導入)よりも、だいぶ見通しがよくなった。Introduce Explaining Variable(説明用変数の導入)では、「price」メソッドに全ての条件分岐が詰め込まれていたが、今回のリファクタリングで、各メソッドに条件分岐を分割することができた。

リファクタリング結果

プロダクトコード(ruby
# before
class Order
  attr_accessor :quantity
  attr_accessor :item_price

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

  def price
    return @quantity * @item_price -
      [0, @quantity - 500].max * @item_price * 0.05 +
      [@quantity * @item_price * 0.1, 100.0].min
  end
end

# after
class Order
  attr_accessor :quantity
  attr_accessor :item_price

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

  def price
    return base_price - quantity_discount + shipping
  end

  def base_price
    @quantity * @item_price
  end

  def quantity_discount
    [0, @quantity - 500].max * @item_price * 0.05
  end

  def shipping
    [base_price * 0.1, 100.0].min
  end
end
テストコード(rspec
# before
describe Order do
  let(:order){ Order.new(quantity, item_price) }

  describe 'price' do
    subject{ order.price }

    context 'quantity < 500' do
      let(:quantity){ 499 }
      context 'quantity * item_price * 0.1 < 100.0' do
        let(:item_price){ 2 }
        it{ should == 1097.8 }
      end
      context 'quantity * item_price * 0.1 >= 100.0' do
        let(:item_price){ 3 }
        it{ should == 1597.0 }
      end
    end

    context 'quantity == 500' do
      let(:quantity){ 500 }
      context 'quantity * item_price * 0.1 < 100.0' do
        let(:item_price){ 1 }
        it{ should == 550.0 }
      end
      context 'quantity * item_price * 0.1 >= 100.0' do
        let(:item_price){ 2 }
        it{ should == 1100.0 }
      end
    end

    context 'quantity > 500' do
      let(:quantity){ 501 }
      context 'quantity * item_price * 0.1 < 100.0' do
        let(:item_price){ 1 }
        it{ should == 551.05 }
      end
      context 'quantity * item_price * 0.1 >= 100.0' do
        let(:item_price){ 2 }
        it{ should == 1101.9 }
      end
    end
  end
end

# after
describe Order do
  let(:order){ Order.new(quantity, item_price) }

  describe 'price' do
    let(:base_price){ 1000 }
    let(:quantity_discount){ 0.1 }
    let(:shipping){ 100 }
    before do
      order.should_receive(:base_price).and_return(base_price)
      order.should_receive(:quantity_discount).and_return(quantity_discount)
      order.should_receive(:shipping).and_return(shipping)
    end
    subject{ order.price }

    it{ should == 1099.9 }
  end

  describe 'base_price' do
    let(:quantity){ 500 }
    let(:item_price){ 2 }
    subject{ order.base_price }
    it{ should == 1000 }
  end

  describe 'quantity_discount' do
    let(:item_price){ 2 }
    subject{ order.quantity_discount }

    context 'quantity < 500' do
      let(:quantity){ 499 }
      it{ should == 0.0 }
    end
    context 'quantity == 500' do
      let(:quantity){ 500 }
      it{ should == 0.0 }
    end
    context 'quantity > 500' do
      let(:quantity){ 501 }
      it{ should == 0.1 }
    end
  end

  describe 'shipping' do
    before do
      order.should_receive(:base_price).and_return(base_price)
    end
    subject{ order.shipping }

    context 'base_price < 1000' do
      let(:base_price){ 999 }
      it{ should == 99.9 }
    end
    context 'base_price == 1000' do
      let(:base_price){ 1000 }
      it{ should == 100.0 }
    end
    context 'base_price > 1000' do
      let(:base_price){ 1001 }
      it{ should == 100.0 }
    end
  end
end

※ 参考資料
 リファクタリング:Rubyエディション6.6 説明用変数の導入(p.146〜147)