-
Notifications
You must be signed in to change notification settings - Fork 0
Chapter 6
本文記述に特に難しいところはないので、手を動かしてコードを確認する。
「サンプル:ローカル変数なし」のコード(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変数の初期化をわかりやすくする」コードとどんどん確認する。
いずれも説明とともに短い紹介コードがあるだけで、「サンプル」コードはない。本文読んで内容確認。記述は別に難しくない。
サンプルのコード。紙面にない@quantityや@item_priceの初期化も追加してある。それのテストコード。
今気づいたのだけどcommit時のコメントが間違っている。これは直す方法があるのだろうか。 ともあれ、先に進むとp.139のbase_priceをメソッドにまとめて、一時変数をインライン化する。 discount_factorに対しても同様の操作を行う。 これはうまく決まると可読性が上がると思う。 ところでリファクタリングの説明のところのコード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. チェイニングしたときに読み易いようにメソッドの名前を変える
というステップである。
こういうコードは本書を読む前からよく書いていたような気がする。説明の通り、特に条件分岐の部分では、長い式を書くよりも、変数名で条件を的確に表現した一時変数を使った方が読みやすいというのは気づいていた。が、「一時変数を軽々しく導入してはならない」というのは特に意識していなかった…。p.145のリファクタリング前のコードとテスト、base_priceを一時変数に置き換えたもの、他に同様の計算をしているところの置き換え、quantity_discountの一時変数化、shippingの一時変数化。
さらに、base_price, quantity_discount, shippingをメソッドとして抽出するところまで。
で、
まだ、他にもリファクタリングすべき箇所がいくつも思いつくだろう。それを続けていただきたい(ハギスを食べるよりはいいはずだ。あの中には訳のわからないものが入っている)。
ということなので、考えてみたが、とりあえず導入した一時変数をメソッド化することくらいしか思いつかなかった。テキストには「いくつも」とあるので他にも明々白々なネタがあるのだろうけど、ここは一旦先に進んで、後でまた戻ってきて考えることにする。
メソッドへの値渡し/参照渡しというのはC言語でポインタを勉強したときに身につけていたので、 def a_method(foo)
foo.modify_in_some_way # 問題なし
foo = another_object # 問題が起きる
end
という解説も特に問題なく把握。
p.151 サンプルのリファクタリング前、引数を一時変数に変えた後。
p.154 サンプルのリファクタリング前、元のプログラムの"ganmma"メソッドを"Ganmma"クラスに置き換え、"Ganmma"クラス内で「メソッドの抽出」リファクタリング。 importantがinportantになっていたり、クラス変数に@がついていなかったりの誤植が散見されるのが残念。 …一読しただけでここはコードを写経してないようだった。 クロージャというものを今ひとつ理解していなかったのでgoogle先生に聞いてみるとMartin Flower先生のblikiの記事がヒットしたのでふむふむと読む。Collection Closure Methodについてもヒット。 この辺、読めば理解はできるんだけど、日常のコードを書いている時にまだまだぱっと使えていない。
どんなプログラムでもたいていループ構造は何箇所かあるので、今後はこの手法が使えないか、意識してチェックする。
本書のコードについてはmanagerのリファクタリング前、リファクタリング後。
officesのリファクタリング前、 リファクタリング後。
managerOfficesのリファクタリング前、リファクタリング後。
salary totalのリファクタリング前、リファクタリング後。
p.162 リファクタリング前、 重複しているうちの片方のメソッドを抽出する、サンドイッチの「具」の部分を外からわたすように変更して、 もう一方のメソッドも同様に書き換える。
(以降、読書中…)