Why Ruby's monkey patching is better than land mines...wait, what?
Why Ruby's monkey patching is better than land mines...wait, what?
Join the DZone community and get the full member experience.Join For Free
Whatever new awaits you, begin it here. In an entirely reimagined Jira.
In the last days, the article Why PHP is better than Ruby has got very popular on DZone. Unfortunately, the majority of popular articles are very controversial, and I feel obliged to write a response on one of the so called "pros" of Ruby: monkey patching.
There are other points to address in the article, like the fact that "everything is an object, even literals" premise is really nothing new; I could hit on the fact that getters setters have gone out of fashion even in Java nowadays, but instead I want to focus on monkey patching as it is a dangerous practice (worse than goto) no one ever talks about.
Let's start from the example.
# the class for integers in ruby
self - adder
## that's correct biyotches, I just turned addition into subtraction
This code has been chosen as an inherently evil example, I think. Or maybe as a powerful example of the freedom that a Ruby programmers has. However, programming is about constraints and models that produce simplifications, not about freedom.
By the way, PHP has some monkey patching capabilities out of the core, via the runkit extension (but that's for userland classes and functions). But you should never, ever use it.
Operator overloading (+ or - defined for YOUR classes) is actually a good idea, as it consists of well-defined syntactic sugar over method calls. I overload everyday in Matlab for example, to add and subtract histograms objects. When the semantic of the operation is really adherent to the original operator, +, - or *, it's a nice idea to reuse that operator.
But redefining methods on native classes, being them for operator overloading or not, is a bomb waiting to explode.
Do you love Singletons and static? Do you unit test your code? Singletons perturb tests since it's usually harder for a unit test to establish a known state. When the code does not call Singletons, the test just has to put together a series of new operator and build a small object graph which will be thrown away after the test itself. When the code contains Singletons, the test must also check that the state of the Singleton is correct, and reset it. However, Singletons are never cited in the Api of the System Under Test, since they are accessed just like global variables instead of being injected.
Monkey patching, like Singletons, is a subtle form of global state, where it's not a bunch of objects hidden somewhere that is modified and alters the results of your test (Singletons), but it's the source code of your classes.
I can't imagine something more evil than this.
A common assumption in object-oriented programming is that native functions and classes are never isolated from your own code via injection. They are just wrapped: nothing is more similar to a list or a string than the real instance. Why this does not cause problems for testability? Because their source code never change (unless you're upgrading your platform, but that's another story.)
Let's suppose we patch SPlObjectStorage::attach(), part of the Standard PHP Library, and add an echo() statement that let us know when an object is added to the collection.
class AMonkeyPatchedTest extends PHPUnit_Framework_TestCase
public function testEchoesElementAdditions()
$container = new SplObjectStorage();
// strange effect: attach echoes something. My What The Frak counter has just increased
Put yourself in the shoes of your colleague reading and executing this test. How do he finds out the code that is running? There is no clue in the code, it's like if the class was calling a singleton. Except that it is a class provided by the language, so it's strange.
What if we pursue a more testable (and well-designed) solution? A possible refactoring can be this:
class ACompositionBasedTest extends PHPUnit_Framework_TestCase
public function testNotifiesObserversOfElementAdditions()
$container = new SplObjectStorage(new EchoElementsAttachingObserver());
// effect: I have to substitute that EchoElementsAttachingObserver if I do not want anything printed
// a Mock, a NullObject...
Even if SplObjectStorage is not my class, I have different options:
- I can subclass it, and override the method (inheritance)
- I can wrap the native data structure and write my own methods, which contain my functionalities (composition: this option is shown in the test.)
Action at a distance and the Open Closed Principle
When modifying source code on the fly, the all-powerful Action at a distance anti-pattern arises:
Action at a distance is an anti-pattern (a recognized common error) in which behavior in one part of a program varies wildly based on difficult or impossible to identify operations in another part of the program. The way to avoid the problems associated with action at a distance are a proper design which avoids global variables and alters data in a controlled and local manner. -- Wikipedia
With monkey patching, you can effectively break other code simply by adding new classes (particularly, by adding new source code which opens up already known classes, being them native or in userland code). This contradicts the Open/Closed Principle:
In object-oriented programming, the open/closed principle states " software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"; that is, such an entity can allow its behaviour to be modified without altering its source code. This is especially valuable in a production environment, where changes to source code may necessitate code reviews, unit tests, and other such procedures to qualify it for use in a product: code obeying the principle doesn't change when it is extended, and therefore needs no such effort.
What happens when you release a monkey patch to a production environment? A golden fail whale?
You can say that you're responsible on what you monkey patch. That you only touch method A on class B, and it is not vital, and that your modification is backward compatible. Then two libraries, and you, override the same method and the application explodes again.
System administrators usually freeze version of libraries on servers in order to avoid modifying the global state of the server: you'll never want to upgrade from PHP 5.2 to PHP 5.3 automatically (or even upgrade at all, if the old application is working well), for fear of compatibilities breaks. Now why would I want change a native class source code on the fly? To get everything else (in my application) that calls that class in peril?
Monkey patching is a practice which involves substituting the pillars of an house: if you're not very careful in what you substitute, the whole building will collapse over your remains. Moreover, you may take down some underground stations full of people as well as a side-effect.
Before insulting a language because it's not possible to monkey patch in it, think about the costs and benefits of adding such a feature. One of the tenets object-oriented programming is message passing between objects as a mechanism of decoupling different parts of the program: there is nothing "decoupled" in changing a class definition which impacts many objects in disparate parts of the object graph.
Opinions expressed by DZone contributors are their own.