イントロダクション
西遊記より
孫悟空にお釈迦様は言われます「私の掌から飛び出すことができれば、お前を自由にしてやる」。悟空は筋斗雲に飛び乗り世界の端を目指します。ここまで来たら大丈夫だろうとそこの山に「悟空参上!」と書きました。お釈迦様に報告した所、お釈迦様の掌にはその文字が書いてありました。
一見奇妙なコード
ある日、肥大化したクラスをリファクタリングしようと処理を分割していた時、不思議な現象に遭遇しました。
class Hoge
def execute
@str = "Hello Ruby!"
hello
end
end
def hello
puts @str
end
hoge = Hoge.new
hoge.execute
# => Hello Ruby!
このコードは問題なく動作して「Hello Ruby!」を出力します。
しかしなぜ分離独立している筈のhelloメソッド内部でHogeクラスのインスタンス変数strを参照できているのでしょうか?他の言語ならエラーになりそうなのに。
何がおきているのか
まずRubyのトップレベルに書いたメソッドがどこに定義されるのかというとKernelモジュールのprivateに定義されます。
(追記:コメントにて指摘されていますが、正しくはObjectに定義されます)
p Kernel.private_methods
# => [:hello, ...]
helloメソッドが追加されていることが確認できます。
このKernelモジュールはObjectクラスにてインクルードされ、さらにすべてのクラスはObjectを継承します。
この関係を確認してみましょう。
p Hoge.ancestors
# => [Hoge, Object, Kernel, BasicObject]
ここまで来ればHogeクラスにもhelloメソッドが定義されていることに気が付くでしょう。
p Hoge.private_methods
# => [:hello, ...]
結論
トップレベルで定義したメソッドは全クラスのプライベートメソッドとなる
helloメソッドはHogeクラスから分離独立しているように見えるが、実際は内部に定義されていたのです。だから普通にインスタンス変数にもアクセスできたのです。
Rubyの基礎階層を理解していないと孫悟空の様にホワッ?となるエピソードでした。