Rubyっぽくリファクタリングするためのポイント
こういうネタに需要があるかはわかりませんが、
自分でリファクタリングをかける時の実例をメモする感覚で...φ(・ω・`)
<今回の仕様>
- data_listは出力したいdataオブジェクトのArray
- ourput(order, data)は、order(1-origin)とdataを受け取ってどこかに出力する
- data.outputable? が真の時のみoutputを呼ぶ
- data.outputable? が偽の時は出力せず、orderも増やさない
これを素直に実装するとこんな感じに
order = 1 data_list.each do |data| if data.outputable? ourput(order, data) order += 1 end end
まず、if文を見ていきますが、どうせ偽のときには何もしないなら、
メソッドの頭で「次」に飛ばしましょう
order = 1 data_list.each do |data| if !data.outputable? next end ourput(order, data) order += 1 end
「if + 単条件=偽」という場合は「unless」を使った方がわかりやすくなります
order = 1 data_list.each do |data| unless data.outputable? next end ourput(order, data) order += 1 end
さらに、この場合は後置で書いた方がきれいです
order = 1 data_list.each do |data| next unless data.outputable? ourput(order, data) order += 1 end
だいぶきれいになってきましたが、次は「order」の部分です
「order += 1」という処理があまりRubyっぽくありません
Arrayクラスには「each_with_index」というメソッドがあるので、
これを使うことを考えてみましょう
考えてみれば、そもそもdata_listが「出力できるdataの集合」ならば、
eachの内部で判定する必要はないのです
# こうしたい some_list.each_with_index do |data, i| ourput(i+1, data) # each_with_indexは0-origin end
ならば、先に出力できるdataを集めましょう
こういうときは Enumerable#select を使います
このメソッドは、渡されたブロックの結果が真であるデータを集めます
order = 1 some_list = data_list.select{|d| d.outputable?} some_list.each do |data| ourput(order, data) order += 1 end
冗長なsome_listを消して、each_with_indexに変えてみます
data_list.select{|d| d.outputable?}.each_with_index do |data, i| ourput(i+1, data) end
どうせブロック内が一行なら、{}に書き換えてしまいますか
data_list.select{|d| d.outputable?}.each_with_index{|d, i| ourput(i+1, d)}
というわけで、7行あったコードがたった1行にΣ(・ω・ノ)ノ
(もっとも、最後に{}に変えるかは、他の処理との兼ね合いで決まるところかと)
ちなみに、Ruby1.8.7以降だと、「each_with_index」ではなく、
「each.with_index」の方が汎用性が高まります
(詳しくは Enumerable::Enumerator を調べてください)
今回の最大のポイントは Enumerable#select だと思います
「値を小さな処理の連結で順に加工していく」
というのが大事なパラダイムで、まさに関数型の発想です
なので、Haskell等の関数型を知ると、Rubyを効率よく書けるようになるというわけです
よく「"i++"と書けるようにしてほしい」と要望があがるそうですが、
Rubyっぽく書いていくと、++なんて書くことはそうないものです
むしろ、そう書きたくなる処理を、今回のようにリファクタリングすべきです
そもそも、Rubyは「Rubyっぽく書いた時」に最適化されているようです
逆にいえば、冗長なコードを書くほど遅くなるということです
リファクタリングがそのまま最適化につながるあたりが重要かと(`・ω・´) b