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. :)
精彩评论