Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 635 posts at DZone. You can read more from them at their website. View Full User Profile

Why Ruby's monkey patching is better than land mines...wait, what?

02.24.2011
| 8026 views |
  • submit to reddit

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
class FixNum
def +(adder)
self - adder
end
end

## 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.

Singletons

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.

<?php
class AMonkeyPatchedTest extends PHPUnit_Framework_TestCase
{
public function testEchoesElementAdditions()
{
$container = new SplObjectStorage();
$container->attach(new stdClass);
// 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:

<?php
class ACompositionBasedTest extends PHPUnit_Framework_TestCase
{
public function testNotifiesObserversOfElementAdditions()
{
$container = new SplObjectStorage(new EchoElementsAttachingObserver());
$container->attach(new stdClass);
// 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?

Incompatibilities

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?

Conclusion

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.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Mike Moore replied on Thu, 2011/02/24 - 9:29pm

You are misquoting Uncle Bob to make a faulty point. He didn't say "programming is about constraints", he said "programming paradigms are about constraints". There is a big difference. As long as you aren't breaking the constraints of encapsulation and polymorphism there is no reason to constrain yourself further.

Ruby's open classes are in no way a violation of the core tennets of OO. Saying that the Open/Closed Principle and "message passing" proves it to be invalid is so backwards I almost don't know where to begin.

Gernot Kogler replied on Fri, 2011/02/25 - 4:29am

I assume that you have no experience in real world ruby / rails development. "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)." Huh? Adding new classes hardly breaks anything. Even adding new methods to a existing class does not break anything, how should it? Rails does it, gems do it all the time. In fact, it's a great way to extend existing classes with new functionality, even at runtime. Overwriting existing methods is another story, but can come handy if you want to modify a certain behaviour of ruby or rails. Of course, you'll have to know what you do in this case. And your app has 100% (or nearly 100%) test coverage, right? So, what's to fear? If your patch breaks anything, a test will tell you. So, please do not talk about things you don't understand.

Giorgio Sironi replied on Fri, 2011/02/25 - 5:56am in response to: Gernot Kogler

"Rails does it, gems do it all the time" There are actually contrary opinions to monkeypatching from Gemcutter contributors: http://avdi.org/devblog/2008/02/23/why-monkeypatching-is-destroying-ruby/ and more classical guys: http://www.codinghorror.com/blog/2008/07/monkeypatching-for-humans.html

Gernot Kogler replied on Fri, 2011/02/25 - 6:39am in response to: Giorgio Sironi

I knew of these posts. But they are 3 years old and ruby and rails are still very successful. So I think they were proven wrong, right? I could give you endless examples how rails and gems do monkey patching. and all is working together very nicely. Here's an example of monkey patching that I did in a gem: I wrote a gem to access xapian (fulltext indexing engine) in a friendlier way. Since the xapian document class is given and since the stored attributes can change from document to document (based on the class that was indexed), I inject accessor methods at runtime into the documents returned from a query to make the stored attributes accessible, to link the document to the indexed model instance etc. How would you do this without monkey patching? And how is that harmful? Monkey patching is an extremely powerful tool and you should use it wisely. But it is by no means evil. Don't you use the rm command because you could wipe your disk completely?

Giorgio Sironi replied on Sun, 2011/02/27 - 9:56am in response to: Gernot Kogler

In PHP or Java I'll use a level of abstraction which does not involve creating new methods, like a getValue($fieldName). :) To transport it on your metaphor, I use mv and rm to handle files but not dd as its dangerousness outweights the benefits that can be achieved with a simple, equivalent Api.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.