読者です 読者をやめる 読者になる 読者になる

安全なモンキーパッチについて(Ruby)

Module#refine を使って、クラス内だけでモンキーパッチすることができます。これでかなり安全だと思います。この例だと、クラス A の中で String のモンキーパッチをやっていますが、これはモジュールの外の名前空間を汚染しません。

自分だけで使うプログラムでは、積極的にモンキーパッチを使ったやり方が好きです。オープンクラスはいかにも Ruby らしい。ただ、他人も使うモジュールなどでは、モンキーパッチによりメソッドがバッティングするかも知れないので、こういうやり方だと「安全」だというわけです。しかし、リスクを負いつつ動的にプログラミングをするというのが Ruby 流なので、Rails みたいな有名なフレームワークだと、名前空間の「汚染」は却って生産的なのでしょうが(Rails よくシラネ)。

class A
  module ExString
    refine String do
      def print_out
        puts self
      end
    end
  end
  
  using ExString
  def b
    "inner".print_out
  end
end

A.new.b    #=>"inner"
"outer".print_out
#=>a.rb:17:in `<main>': undefined method `print_out' for "outer":String (NoMethodError)
using A::ExString
"outer".print_out    #=>"outer"

しかし、ここで

"outer".b

と意味のわからぬことをすると、NoMethodError がでません。というか、ただ "outer" が返されます。という仕様がよくわからない(Ruby 2.2.3)。バグじゃないの?*1

クラスの中にモジュールを入れるのは、以下のような場合があるため。こうしないと、A.new.b の挙動が変ってしまう可能性がある。

module ExString
  refine String do
    def print_out
     puts self
    end
  end
end

class A  
  using ExString
  def b
    "inner".print_out
  end
end

A.new.b    #=>"inner"

module ExString
  refine String do
    def print_out
     puts "test"
    end
  end
end

 A.new.b       #=>"test"

*1:と思ったら、String#b という組み込みメソッドがちゃんとありました(参照)。これは気づかなかったなあ。知らぬ間にオーバーライドしていたわけだ。くれぐれも意図しないオーバーライドには気をつけましょうという話でした。