(The title of this post is intended to be deliberately provocative, as well as being a nod to Steven Colbert’s “The People Destroying America” segments. It’s provocative because I want to get people talking about this issue. I don’t actually think that monkey patching is “destroying” Ruby, but I do think the proliferation of the technique has real and troubling implications for Ruby’s future.)
“Monkey patching”, for anyone who doesn’t know, refers to the practice of extending or modifying existing code by changing classes at run-time. It is a powerful technique that has become popular in the Ruby community at least in part because the Ruby language makes it so easy. Any class can be re-opened at any time and amended in any way.
I believe the term first arose in the Python community, as a derogatory term for a practice which that community tended to frown on. The Ruby community, on the other hand, has embraced the term and the practice with enthusiasm. I’m starting to think that the Pythonistas’ attitude may have been justified.
Here’s what crystalized it for me. The other day I wrote a small Rails plugin (“NullDB”:nulldb). It was inspired largely by another plugin, “UnitRecord”:unitrecord. UnitRecord is by “Dan Manges”:[http://www.dcmanges.com/], a talented Rails developer whom I have a lot of respect for.
UnitRecord is implemented almost entirely as a set of monkey patches. When invoked, it dynamically modifies several standard Ruby and Rails classes, including @ActiveRecord::[email protected], @Test::Unit::[email protected] As a result of this implementation, it is tightly coupled to the inner workings of ActiveRecord. A small change to Rails and it could cease to work, and such a failure would be difficult to debug. Indeed, one of the reasons I decided to write “NullDB”:nulldb was because of just such a failure.
In writing “NullDB”:nulldb, I discovered that I could achieve the same functionality without resorting to monkey patching. Instead of modifying existing classes, it implements the Rails “Database Adapter API”:[http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/AbstractAdapter.html]. The finished library is shorter than “UnitRecord”:unitrecord, is composed entirely of implemetations of public APIs, and contains zero monkey patches. The code is also easier to understand, in my opinion, because of the lack of metaprogramming.
Now, I did not write this post to gloat about how my library is better than Dan’s. As I said before, I have a lot of respect for Dan. He’s a smart guy; I’ve learned a great deal from “his blog”:[http://www.dcmanges.com/]; and it’s safe to say I would be a poorer Rails programmer if it weren’t for hm.
And this is really the point. Monkey patching is the new black. it’s what all the hip kids are doing. To the point that smart, experienced hackers reach for a monkey patch as their tool of first resort, even when a simpler, more traditional solution is possible.
I don’t believe this situation to be sustainable. Where I work, we are already seeing subtle, difficult-to-debug problems crop up as the result of monkey patching in plugins. Patches interact in unpredictable, combinatoric ways. And by their nature, bugs caused by monkey patches are more difficult to track down than those introduced by more traditional classes and methods. As just one example: on one project, it was a known caveat that we could not rely on class inheritable attributes as provided by ActiveSupport. No one knew why. Every Model we wrote had to use awkward workarounds. Eventually we tracked it down in a plugin that generated admin consoles. It was overwriting @Class.inherited()@. It took us months to find this out.
This is just going to get worse if we don’t do something about it. And the “something” is going to have to be a cultural shift, not a technical fix. I believe it is time for experienced Ruby programmers to wean ourselves off of monkey patching, and start demonstrating more robust techniques.
I see the problem as one of convenience: sure, Ruby provides us with an immense toolbox, bigger than that of most other languages. But we’re like lazy carpenters: sure, we have a whole garage full of tools, but there’s a hammer laying right there on the floor next to us, and it’s easier to reach for the hammer instead of getting up and fetching the right tool for the job. But those tools do exist. Indeed, most of the patterns in the “software pattern literature”:[http://c2.com/ppr/index.html] are inspired at least partly by the need to manage extension in robust and maintainable ways.
I look at the Emacs community as an inspiration. Emacs, as many of you probably know, is essentially just a lisp machine. Emacs Lisp, the language it is written in, is every bit as dynamic as Ruby. Functions can be replaced at any time, by code in any package. And because all Emacs Lisp code is intended to extend the Emacs editor, it is not uncommon for hundreds of different Emacs Lisp packages to be running in the same process, all coexisting and interacting. And yet, for the most part this melting pot of extensions all function together smoothly and without breakage.
Why? I think the biggest reason is community conventions. I’ve read a lot of ELisp code, and I have very rarely seen the equivalent of Ruby-style monkey patching. Even the @[email protected] mechanism, an AOP(Aspect Oriented Programming)-like feature which enables functions to be dynamically wrapped and chained, somewhat like Rails’ @[email protected], is used sparingly. Instead, every mature Emacs extension exposes a plethora of “hooks”, extension points that other packages can attach their own handlers to. Other packages add their handlers to these hooks, and to hooks that the core Emacs code provides, and thus they cooperate largely without collisions.
Hooks are just one of the techniques available to us for robustly handling extension in a language as dynamic and powerful as Ruby. Another is using the dependency injection style to decouple classes from each other and allow third parties to substitute their own classes without monkey patching. Another un-sexy but important practice is simply writing clear and comprehensive documentation of our classes’ APIs and extension points.
But the most important thing we can do is set an example. This is my call to action, my line in the sand: as experienced Ruby programmers, let us demonstrate solid, sustainable practices in our own code. Let us use monkey patching sparingly, as a tool for exploration and experiment, and only in production code as a last resort. Let’s produce APIs that are amenable to extension without having to be patched. And let us begin to develop some community-wide conventions and tools for writing extensible classes.
fn1. UPDATE A commenter on ruby-talk felt that my use of “Dependency Injection” revealed an “Enterprisey” bias. Let me be clear that when I say “dependency injection style”, I’m not talking about using elaborate DI frameworks. I’m just talking about writing classes that allow their collaborators to be passed in, either via constructors or setters, rather than having hard-coded collaborators.[nulldb]http://avdi.org/projects/nulldb/ [unitrecord]http://unit-test-ar.rubyforge.org/