ぱろっと・すたじお

技術メモなどをまったりと / my site : http://parrot-studio.com/

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