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)

こんな具合い。