In Ruby, programmers are allowed to change predefined classes. So a really bad programmer could do something like:
class String
def ==(other)
return true
end
end
Obviously, almost no one would be quite this dumb, but the idea that more subtle changes to a predefined class could cause problems in already-working code seems to me to violate the principle of encapsulation.
Four questions:
I know this is a somewhat subjective question, but I'd really like to know how the wider programming community feels about this so called "monkey patching."
This is possible because Ruby supports a concept known as “Open classes”, which lets you do exactly this, i.e. extend an existing class, without altering the original class-block. “Monkey Patching” is something that's made possible thanks to the concept of open classes.
Encapsulation is defined as the wrapping up of data under a single unit. It is the mechanism that binds together code and the data it manipulates. In a different way, encapsulation is a protective shield that prevents the data from being accessed by the code outside this shield.
Advertisements. Ruby is a pure object-oriented language and everything appears to Ruby as an object. Every value in Ruby is an object, even the most primitive things: strings, numbers and even true and false.
First, does this, in fact, violate the OO principle of encapsulation?
Yes.
Second, is there a way, as a programmer, that I can guarantee in my code that I am working with an unmodified version of a class?
Not yet. Classboxes in Ruby 2.0 are (hopefully) going to be the solution.
Third, should I ever be "opening" classes in my code, for any reason?
Only as a last resort.
You should never monkey-patch your own classes. There's simply no point. You control them, you can make them do what you want in the first place.
You should never monkey-patch classes in a library. (The exception to this rule are libraries whose sole purpose it is to monkey-patch something, e.g. Marc-André Lafortune's backports
library, which monkey-patches Ruby 1.8.6, 1.8.7, 1.9.0 and 1.9.1 with as much as possible of the functionality from Ruby 1.9.2.) You may provide an add-on library which provides monkey-patches that make it easier to use your library (e.g. you have an encryption library which provides a Kryptonite.encrypt(str)
method and you provide an add-on String#encrypt
method), but that add-on should be in a separate library, which the user needs to explicitly require
. It should be fully optional.
You should not monkey-patch core classes. This refers to classes like Array
or Symbol
in Ruby, but for a Rails library, I would also include classes like ActiveRecord::Base
under the "core" label. (Same caveat as above. E.g. in versions of Rails before 3.0, there was no well-defined plugin API, monkey-patching was the only way to extend Rails. Without people who broke this rule, there would never haven been any plugins, and Rails would never be where it is now.)
Try inheritance first. Try composition (wrappers, proxies, facades, adapters, …) first. Try refactoring first. Try helper objects first. Only if that doesn't work, turn to monkey-patching.
Be respectful when you monkey-patch: if you are adding a new method, make sure it doesn't already exist, and deal with it if it does (e.g. call it from your method). If you are wrapping an existing method, make sure that if somebody else has already wrapped it, their wrapper gets called and that when somebody wants to wrap it afterwards, your wrapper allows that. (In particular, this means you must preserve the method's existing contract.)
Put your monkey-patches in a mixin, if at all possible. That way, they show up in the inheritance chain, which will give anybody who tries to debug the code a fighting chance to figure out what is going on. Put your monkey-patches in separate, obviously named files.
Finally, how is this sort of thing handled in a large-scale, production coding environment? In other words, do people in the programming industry actually do this in code that others will use? Or even if they don't, how do you ensure that some plugin author somewhere isn't doing something like this that will ruin an essential part of your program?
Don't work with "really bad programmers", as you call them.
As simplistic as this sounds, that's basically what it boils down to. Yes, of course, you can write tests, do code reviews, practice pair programming, use static analysis tools, run your code with warnings enabled (e.g. the code you posted in your question will generate a warning: method redefined; discarding old ==
). But for me, that's all something that a not-really-bad-programmer would do anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With