evalやめる
コードを読んでいてevalが出てくるとおやと引っかかる。
evalしないとどうしようもないこともある。しかしeval以外ではどうにもできないことを、あえてしなければならい状況は、そうそうあるものではないと思う。実際、よく見掛けるのは動的なインスタンス変数の参照や、メソッド呼び出しのために使われているケースだったりする。
@foo = "baz"
def bar(arg = nil)
arg || @foo
end
BAZ = "baz"
# インスタンス変数の参照
ivar_name = "@foo"
eval ivar_name #=> "baz"
instance_variable_get ivar_name #=> "baz"
# メソッド呼び出し
method_name = "bar"
eval method_name #=> "baz"
send method_name #=> "baz"
# 引数付きのメソッド呼び出し
eval "#{method_name}(\"foo_#{@foo}\")" #=> "foo_baz"
send method_name, "foo_#{@foo}" #=> "foo_baz"
# 定数の参照
name = "Baz"
eval name.upcase #=> "baz"
Object.const_get name.upcase #=> "baz"
# シンボルの生成
eval ":foo_bar_#{@foo}" #=> :foo_bar_baz
:"foo_bar_#{@foo}" #=> :foo_bar_baz
"foo_bar_#{@foo}".to_sym #=> :foo_bar_baz
といった具合い。
こういう動的な参照とか呼び出しとかをするケースでは、evalでなければ大丈夫とはいえないこともある。
たとえば、よく知られているように、privateメソッドでもsendを使え呼び出せてしまう。それでも何が起こるのかまったく読めないevalよりはマシなので、なるべく使いたくないですねと。
それからevalは遅い。こういうのは実際のケースで測定しないと意味が薄まるだろうけども、参考までに単純なコードを100,000回まわしたときにかかった時間を測定してみた。
user system total real
eval "@foo" 0.490000 0.000000 0.490000 ( 0.491319)
instance_variable_get "@foo" 0.040000 0.000000 0.040000 ( 0.032533)
eval "bar" 0.570000 0.000000 0.570000 ( 0.575651)
send "bar" 0.040000 0.000000 0.040000 ( 0.037645)
eval "bar(\"foo_#{@foo}\")" 0.780000 0.000000 0.780000 ( 0.780496)
send "bar", "foo_#{@foo}" 0.030000 0.000000 0.030000 ( 0.032074)
eval @foo.upcase 0.550000 0.000000 0.550000 ( 0.549882)
Object.const_get @foo.upcase 0.040000 0.000000 0.040000 ( 0.039576)
eval ":foo_bar_#{@foo}" 0.580000 0.000000 0.580000 ( 0.574733)
:"foo_bar_#{@foo}" 0.040000 0.000000 0.040000 ( 0.040932)
"foo_bar_#{@foo}".to_sym 0.050000 0.000000 0.050000 ( 0.040794)
こんな具合い。