Chapter 6

kono edited this page Sep 14, 2010 · 10 revisions
Clone this wiki locally

第6章 メソッドの構成方法

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

本文記述に特に難しいところはないので、手を動かしてコードを確認する。
「サンプル:ローカル変数なし」のコード(p.130)は、紙面に載っているのはメソッド一つだけで、@orders等どこか他所で初期化・代入されている変数もあって、このままではテストできないので、適当に補ってクラスをでっち上げた

それが下記ソース。

class Extract_Method_Order
  attr_reader :amount
  def initialize(amount)
    @amount = amount
  end
end

class Extract_Method
  def initialize(name)
    @name=name
    @orders=Array.new
  end
  
  def order_entry(amount)
    @orders << Extract_Method_Order.new(amount)
  end
  
  def print_owing
    outstanding = 0.0
  
    #バナーを出力(print banner)
    puts "*************************"
    puts "***** Customer Owes *****"
    puts "*************************"
  
    #勘定を計算(calculate outstanding)
    @orders.each do |order|
      outstanding += order.amount
    end
  
    #詳細を表示(print details)
    puts "name: #{@name}"
    puts "amount: #{outstanding}"
  end

end

このソースをテストするコードは下記。

require 'rubygems'
require 'test/unit'
require 'must'
require 'kconv'
require 'tempfile'
require 'Extract_Method'

$KCODE="UTF8"
class TC_S6_1_with_local_val < Test::Unit::TestCase

  def assert(status,msg)
    if(RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|cygwin|bccwin/)
      msg=Kconv.tosjis(msg)
    end
    super(status,msg)
  end

  def setup
    @obj = Extract_Method.new("kono")
    @obj.order_entry(10)
    @obj.order_entry(20)
    @obj.order_entry(30)
  end
  
  must "test1" do
    temp=Tempfile::new("foobar")
    $stdout=temp
    @obj.print_owing
    $stdout=STDOUT
    temp.close
    temp.open
    result= temp.read
    expectation = "*************************\n***** Customer Owes *****\n*************************\nname: kono\namount: 60.0\n"
    temp.close
    assert_equal(result, expectation)
  end
end

標準出力の内容をチェックするのに、$stdoutをTempfileにリダイレクトして、後でそのTempfileの内容を確認するようにした。

あとは特に難しいところはなく、p.131-132のコードp.132-133のコード,
p.133のcalculate_outstandingをinjectを使って書き直したコード…と紙面にそってコードを書き、テストをする。

この過程で、 print_banner, print_details, calculate_outstandingの各メソッドが、誤ってclass Extract_Methodの外に出てしまったのだけど、テストが通っていたのでしばらく気付かなかった。

それを直して、p.133下部のprint_owingが引数を取るコード(リファクタリング前)同リファクタリング後, p.134の「outstanding変数の初期化をわかりやすくする」コードとどんどん確認する。

6.2 メソッドのインライン化(Inline Method)

6.3 一時変数のインライン化(Inline Temp)

いずれも説明とともに短い紹介コードがあるだけで、「サンプル」コードはない。本文読んで内容確認。記述は別に難しくない。

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

サンプルのコード。紙面にない@quantityや@item_priceの初期化も追加してある。それのテストコード

今気づいたのだけどcommit時のコメントが間違っている。これは直す方法があるのだろうか。 ともあれ、先に進むとp.139のbase_priceをメソッドにまとめて一時変数をインライン化するdiscount_factorに対しても同様の操作を行う。

6.5 一時変数からチェインへ(Replace Temp with Chain)

これはうまく決まると可読性が上がると思う。 ところでリファクタリングの説明のところのコード
mock = Mock.new

というのは何かのモックなのだろうけど、Flex MockでもMockaでもないみたいだが、どんなモックなのだろう…?

閑話休題。p.141下部のサンプルのコード(リファクタリング前)。テストの都合上

  select = Select.new
  select.add_option(1999)
  select.add_option(2000)
  select.add_option(2001)
  select.add_option(2002)

の部分をmain_procというメソッドの中に入れた。そのテストコード

このリファクタリングは3段階で行われる。

1.Selectのインスタンスを作ってオプションを追加するメソッドを作成する
2. オプションを追加するメソッドを書き換えてselfを返すようにしてチェイニングできるようにする
3. チェイニングしたときに読み易いようにメソッドの名前を変える

というステップである。

6.6 説明用変数の導入(Introduce Explaining Variable)

こういうコードは本書を読む前からよく書いていたような気がする。説明の通り、特に条件分岐の部分では、長い式を書くよりも、変数名で条件を的確に表現した一時変数を使った方が読みやすいというのは気づいていた。が、「一時変数を軽々しく導入してはならない」というのは特に意識していなかった…。

p.145のリファクタリング前のコードとテストbase_priceを一時変数に置き換えたもの他に同様の計算をしているところの置き換えquantity_discountの一時変数化shippingの一時変数化
さらに、base_price, quantity_discount, shippingをメソッドとして抽出するところまで。

6.7 一時変数の分割(Split Temporary Variable)

あーこれは意識していなかった。心がけよう。 サンプルで説明してあるハギスという料理を食べてみたい。 p.148のリファクタリング前のコードとテストp.148-149の「最初の代入」の一時変数名の変更p.149の「第二の代入」の一時変数名の変更

で、


まだ、他にもリファクタリングすべき箇所がいくつも思いつくだろう。それを続けていただきたい(ハギスを食べるよりはいいはずだ。あの中には訳のわからないものが入っている)。

ということなので、考えてみたが、とりあえず導入した一時変数をメソッド化することくらいしか思いつかなかった。テキストには「いくつも」とあるので他にも明々白々なネタがあるのだろうけど、ここは一旦先に進んで、後でまた戻ってきて考えることにする。

6.8 引数への代入の除去(Remove Assignments to Parameters)

メソッドへの値渡し/参照渡しというのはC言語でポインタを勉強したときに身につけていたので、
 def a_method(foo)
    foo.modify_in_some_way    # 問題なし
    foo = another_object            # 問題が起きる
end

という解説も特に問題なく把握。

p.151 サンプルのリファクタリング前引数を一時変数に変えた後

6.9 メソッドからメソッドオブジェクトへ(Replace Method with Method Object)

p.154 サンプルのリファクタリング前元のプログラムの"ganmma"メソッドを"Ganmma"クラスに置き換え"Ganmma"クラス内で「メソッドの抽出」リファクタリング。 importantがinportantになっていたり、クラス変数に@がついていなかったりの誤植が散見されるのが残念。

6.10 アルゴリズム変更(Substitute Algorithm)

…一読しただけでここはコードを写経してないようだった。

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

クロージャというものを今ひとつ理解していなかったのでgoogle先生に聞いてみるとMartin Flower先生のblikiの記事がヒットしたのでふむふむと読む。Collection Closure Methodについてもヒット

 この辺、読めば理解はできるんだけど、日常のコードを書いている時にまだまだぱっと使えていない。
どんなプログラムでもたいていループ構造は何箇所かあるので、今後はこの手法が使えないか、意識してチェックする。
 本書のコードについてはmanagerのリファクタリング前リファクタリング後
officesのリファクタリング前リファクタリング後
managerOfficesのリファクタリング前リファクタリング後
salary totalのリファクタリング前リファクタリング後

6.12 サンドイッチメソッドの抽出(Extract Surrounding Method)

サンドイッチメソッドとは「ほぼ同じコードによる2つのメソッドがあり、違いはそのメソッドのちょうど中頃にある」ものをリファクタリングしたもので、共通部分をサンドイッチのパンに、異なる部分を「具」に見立てたものらしい。 原文は"Surrounding Method"だから、うまい訳だと思うけど、この手の訳語はある程度普及してもらわないと困るのでぜひ頑張ってほしい。 リファクタリングにあたっては予想通りyieldが大活躍。 テストコードのデータは『バブリング創世記』より借りた。

p.162 リファクタリング前重複しているうちの片方のメソッドを抽出するサンドイッチの「具」の部分を外からわたすように変更して、 もう一方のメソッドも同様に書き換える

(以降、読書中…)