こんな感じの“イケてない”と称されるコードを改善する話。
def total_sales_within_date_rangeRubyのリファクタリングでイケてないコードを美しいオブジェクト指向設計のコードへ改良するための方法 - その1 - ベルリンのITスタートアップで働くジャバ・ザ・ハットリの日記
orders_within_range = []
@orders.each do |order|
if order.placed_at >= @start_date && order.placed_at <= @end_date
orders_within_range << order
end
endsum = 0
orders_within_range.each do |order|
sum += order.amount
end
sum
end
元記事では、Smalltalk 由来のいわゆる「〜ect系」メソッドの導入によりコードをシンプルに書き換えていますが、もうちょっと Ruby や Rails に備わっている機能を使うことはできないのかなぁ、とリファレンスを紐解きながらこんなふうにしてみました。
def total_sales_within_date_range within_date_range = ->order{ order.placed_at.between?(@start_date, @end_date) } @orders.select(&within_date_range).sum(&:amount) end
範囲に収まっているかどうかの判定は無名関数(Proc)にして名前を付け、select に渡しています。Ruby の無名関数は Smalltalk のと違い、〜ect系メソッドの引数としてはそのまま渡せないので、& を付ける必要があります。
範囲に収まっているかどうかの判定処理記述の中身についても、Date が Numeric 同様 Comparable なのを利用して簡潔な between? に置き換えています。Smalltalk にも Magnitude>>#between:and: がありますね。
map(&:amount).inject(0, :+) も冗長で意図が伝わりにくいので sum ひとつに置き換えました。ただ、ここで使った sum は Smalltalk の sum とは違って、次のような定義を想定しています。Rails や Ruby2.4 の sum はよく知らないので、こういう動きでなかったらごめんなさい。
class Array def sum(zero = 0, &b) inject(zero){ | s, e | s + (b ? b[e] : e) } end end
Ruby の制約として残念だったのは、Proc の within_date_range をクエスチョンマークを使って within_date_range? としたかったのが許されなかったところ。メソッド名にすればクエスチョンマークもOKなのですが、そうすると今度は select の引数にするときに記述が面倒になるので痛し痒しですね。
Rubyist are the ones who would benefit the most from learning Smalltalk. As @avdi pointed out several times. https://t.co/n69Jaa4wdd
— Esteban A. Maringolo (@emaringolo) 2016年7月18日
How learning Smalltalk can make you a better OO and Ruby programmer https://t.co/6nZ6cU5TTS
— Ruby News (@ruby_news) 2016年7月18日