アプリケーション稼働中ではなくて準備中の場合はevalを使ってもよい
Jay Fields' Thoughts: Move eval from Run-time to Parse-time
Rubyにおいて、実行時にevalするのはパフォーマンスが問題になることがある。文字列を解釈するコストが馬鹿にならないのだ。だからevalは極力避けるべき…なんだが例外がある。
ウェブアプリケーションなど起動時間はさほど問題ではなく、長時間稼動させるアプリケーションの場合、準備には多少時間をかけてよい。だから稼働中に実行するevalを準備中に移動するのは有効なリファクタリングだ。
Person.attr_with_defaultはデフォルト値つきのattr_readerだ。Person#emailsとPerson#employee_numberがデフォルト値つきで定義される。しかし、これらのメソッドを呼んだだけでevalされるのは効率面で問題だ。
class Person def self.attr_with_default(options) options.each_pair do |attribute, default_value| define_method attribute do eval "@#{attribute} ||= #{default_value}" end end end attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next" end
リファクタリングして以下のようなコードにする。Module#define_methodをevalに置き換えたのだ。一瞬ぎょっとするかもしれないが、これは正しいリファクタリングだ。Person.attr_with_defaultは稼動時ではなくて準備中(定義した直後)に実行されるからだ。Person.attr_with_defaultの中でevalしてメソッドを定義している。
class Person def self.attr_with_default(options) options.each_pair do |attribute, default_value| eval "def #{attribute} @#{attribute} ||= #{default_value} end" end end attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next" end
まぁ俺なら
module_eval %{ def #{attribute} @#{attribute} ||= #{default_value} end" }
と書くがな。Kernel#evalでもいいがModule#module_eval萌えだから。あと%{}がブロックらしく見えるので。
この場合ならObject#instance_variable_getとObject#instance_variable_setで書けるとコメントに書いてあった。まぁ当然のツッコミなんだが。でもメソッド名が長すぎるんだよね…
っつーか、準備中のevalで思い出したが、それってD言語のmixinじゃん!D言語のmixinはコンパイル時限定evalだから、コンパイラ言語なのにいろいろおもしろいことできるんだよね。