开发者

Gotchas for redefining methods in ruby

What are the things to be c开发者_StackOverflow社区areful about while redefining methods in Ruby? Is redefining core library methods okay?


the problems IMHO are

  • You'll forget about the change.
  • You'll copy paste a snippet from the internet, which will trigger an error the altered behavior and you'll scratch your head until you get hairless patches.
  • Another developer will come after your and fight a bug for 3 months, until he finds it's in one of the monkey patches. He'll go to HR, get your address and and show you why not to do monkey patches.

Now, sometimes you need to monkey patch a class (even in the core library, why not). My suggestion is

  • Put all of your monkey-patches in ONE source folder.
  • The second thing you say to a new developer after "Hello my name is ..." is the location of that folder and a detailed explanation of what each monkey patch does.


I don't do much monkeypatching myself, but I've heard that rather than doing

class String
  def improved_method
    # teh codes
  end
end

It's better to put the new method into a module, and then include the module

module ImprovedString
  def improved_method
    # teh codes
  end
end

class String
  include ImprovedString
end

it makes it easier to find where a method has been defined, and the old version still exists without having to do alias chaining.


I like the other answers. Though, I have to add that:

Sometimes you may only want to redefine methods only for certain instances. You can do this, and it makes it somehow more controlled than changing the functionality for all objects of a certain class - as long as a proper debugger is used for debugging:

class << object_instance
  def method_redefinition
    return "method_redefinition"
  end
end
object_instance.method_redefinition => "method redefinition"

The metioned set of functionalities can also be encapsulated in a mix-in in order to avoid too much nesting and messy "code definition inside code execution":

module M
  def method_redefinition
     "method_redefinition"
  end
end
object_instance.extend M
object_instance.method_redefinition => "method_redefinition"


You're talking about monkey patching and it's dangerous for the following reasons according to wikipedia

Carelessly written or poorly documented monkey patches can lead to problems:

  • They can lead to upgrade problems when the patch makes assumptions about the patched object that are no longer true; if the product you have changed changes with a new release it may very well break your patch. For this reason monkey patches are often made conditional, and only applied if appropriate.
  • If two modules attempt to monkey-patch the same method, one of them (whichever one runs last) "wins" and the other patch has no effect, unless monkeypatches are written with pattern like alias_method_chain
  • They create a discrepancy between the original source code on disk and the observed behaviour that can be very confusing to anyone unaware of the patches' existence.

Even if monkey patching isn't used, some see a problem with the availability of the feature, since the ability to use monkey patching in a programming language is incompatible with enforcing strong encapsulation, as required by the object-capability model, between objects.

There's talk of a safer way of monkey patching coming in ruby 2 called refinements


This tiny gem might be useful if you're finding yourself running into monkey-patching issues: (https://github.com/gnovos/ctx). I originally wrote it to create more expressive DSLs by allowing alterations to the base objects without doing too much damage elsewhere, but it can probably be put to any number of uses. It gets around some of the monkey-patching issues by scoping method re-definition into arbitrary contexts that can be swapped out as needed.

If I want to redefine a method in some core class (for example, in String, etc), I use ctx_define instead of "def" and then later wrap the section of code that should use the new definition in a ctx block, like so:

class ::String
  ctx_define :dsl, :+ do |other|
    "#{self[0].upcase}#{self[1..-1]}#{other.capitalize}"
  end
end

puts "this" + "is" + "normal" + "text" + "concatination"
# => thisisnormaltextconcatination

ctx(:dsl) { puts "this" + "is" + "special" + "text" + "concatination" }
# => ThisIsSpecialTextConcatination

I only threw it together in a few minutes, so I can't make any guarantees about how robust it is in any number of complicated situations, but it seems to work fine for simple needs. Give it a look if you're interested and see if it is of any help. :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜