29

Some languages like Ruby and JavaScript have open classes which allow you to modify interfaces of even core classes like numbers, strings, arrays, etc. Obviously doing so could confuse others who are familiar with the API but is there a good reason to avoid it otherwise, assuming that you are adding to the interface and not changing existing behavior?

For example, it might be nice to add a an Array.map implementation to web browsers which don't implement ECMAScript 5th edition (and if you don't need all of jQuery). Or your Ruby arrays might benefit from a "sum" convenience method which uses "inject". As long as the changes are isolated to your systems (e.g. not part of a software package you release for distribution) is there a good reason not to take advantage of this language feature?

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
maerics
  • 151,642
  • 46
  • 269
  • 291
  • 2
    FWIW, There's a proposition for including something called "refinements" in Ruby 2.0 which would mitigate the pitfalls associated with monkey-patching. Just google for "ruby refinements", there are couple of articles around. – Mladen Jablanović Apr 21 '11 at 12:13

6 Answers6

32

Monkey-patching, like many tools in the programming toolbox, can be used both for good and for evil. The question is where, on balance, such tools tend to be most used. In my experience with Ruby the balance weighs heavily on the "evil" side.

So what's an "evil" use of monkey-patching? Well, monkey-patching in general leaves you wide open to major, potentially undiagnosable clashes. I have a class A. I have some kind of monkey-patching module MB that patches A to include method1, method2 and method3. I have another monkey-patching module MC that also patches A to include a method2, method3 and method4. Now I'm in a bind. I call instance_of_A.method2: whose method gets called? The answer to that can depend on a lot of factors:

  1. In which order did I bring in the patching modules?
  2. Are the patches applied right off or in some kind of conditional circumstance?
  3. AAAAAAARGH! THE SPIDERS ARE EATING MY EYEBALLS OUT FROM THE INSIDE!

OK, so #3 is perhaps a tad over-melodramatic....

Anyway, that's the problem with monkey-patching: horrible clashing problems. Given the highly-dynamic nature of the languages that typically support it you're already faced with a lot of potential "spooky action at a distance" problems; monkey-patching just adds to these.

Having monkey-patching available is nice if you're a responsible developer. Unfortunately, IME, what tends to happen is that someone sees monkey-patching and says, "Sweet! I'll just monkey-patch this in instead of checking to see if other mechanisms might not be more appropriate." This is a situation roughly analogous to Lisp code bases created by people who reach for macros before they think of just doing it as a function.

JUST MY correct OPINION
  • 35,674
  • 17
  • 77
  • 99
  • I'm more curious about when it can be used for good. The evil side of the coin is obvious, but when is it actually more useful than changing your program to accept whatever thing you're patching as an argument? I mean, we have these architectural patterns and it seems that things like monkey patching exist only to subvert them for subversion's own sake. – weberc2 Dec 17 '15 at 22:17
  • First of all most languages are imperative except purely functional languages, and so every programming language has "action at a distance" in some from or another, although yes you shouldn't use it when you don't need it. Monkey patching is good for testing or mocking out behavior. They can be localized in factory/class decorators/metaclasses where they create a patched new class/object from another object to help with "cross-cutting concerns" in between *ALL* methods like logging/memoization/caching/database/persistance/unit conversion. – aoeu256 May 10 '21 at 14:02
  • 1
    @weberc2 Hi! I know it's seven years later but here's a use case for monkey-patching: changing a 3rd-party library! Maybe "good" is not the right word. But, if you absolutely, positively have to use the library, and it just literally cannot do what you want it to out of the box, well, this is an available option. – Charles Wood Oct 25 '22 at 21:49
23

Wikipedia has a short summary of the pitfalls of monkey-patching:

http://en.wikipedia.org/wiki/Monkey_patch#Pitfalls

There's a time and place for everything, also for monkey-patching. Experienced developers have many techniques up their sleeves and learn when to use them. It's seldom a technique per se that's "evil", just inconsiderate use of it.

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • 1
    eh, it's one of those things "if everyone were to do that", like littering on the highway – Alexander Mills Apr 04 '15 at 06:40
  • "Experienced developers have many techniques up their sleeves and learn when to use them" <- I'm not sure there's ever a "time and a place for monkey patching". When is it better than passing the varying thing as an argument, for example? – weberc2 Dec 17 '15 at 22:16
  • @weberc2 Nice example of monkey-patching is a benchmark of third-party lib's function. Add timer that starts before function execution and finished after. Why not? – S Panfilov May 26 '16 at 05:24
  • @SergeyPanfilov Unless I misunderstand you, I don't see any reason to patch. Just start a timer before you call the function and stop it afterward. Perhaps you have some more nuanced use case? – weberc2 May 26 '16 at 16:16
  • @weberc2 Sure. Once I used monkey-patching to patch third-party socket client. Those client uses callbacks but I want it to be able to work with promises. Just like `client.success().error()` instead of `client(callback)`. I was need this because I want to work with those socket client in the same way as I do it with a rest client used in those project. Besides of all nice article: http://me.dt.in.th/page/JavaScript-override/ – S Panfilov May 27 '16 at 00:31
  • At times, I feel *really* tempted to monkey-patch all the Array methods from lodash onto Array.prototype... just to make the syntax a bit nicer and avoid require'ing lodash into virtually every module. Resisted the temptation so far... – Mark K Cowan Jan 16 '17 at 21:11
  • I'm seeing a few JS oriented comments so I'd like to strech that with the introduction of Symbols with ES6, it seems to me that monkey-patching has become harmless – theFreedomBanana Aug 30 '19 at 12:09
6

With regards to Javascript:

is there a good reason to avoid it otherwise, assuming that you are adding to the interface and not changing existing behavior?

Yes. Worst-case, even if you don't alter existing behavior, you could damage the future syntax of the language.

This is exactly what happened with Array.prototype.flatten and Array.prototype.contains. In short, the specification was written up for those methods, their proposals got to stage 3, and then browsers started shipping it. But, in both cases, it was found that there were ancient libraries which patched the built-in Array object with their own methods with the same name as the new methods, and had different behavior; as a result, websites broke, the browsers had to back out of their implementations of the new methods, and the specification had to be edited. (The methods were renamed.)

If you mutate a built-in object like Array on your own browser, on your own computer, that's fine. (This is a very useful technique for userscripts.) If you mutate a built-in object on your public-facing site, that's less fine - it may eventually result in problems like the above. If you happen to control a big site (like stackoverflow.com) and you mutate a built-in object, you can almost guarantee that browsers will refuse to implement new features/methods which break your site (because then users of that browser will not be able to use your site, and they will be more likely to migrate to a different browser). (see here for an explanation of these sorts of interactions between the specification writers and browser makers)

All that said, with regards to the specific example in your question:

For example, it might be nice to add a an Array.map implementation to web browsers which don't implement ECMAScript 5th edition

This is a very common and trustworthy technique, called a polyfill.

A polyfill is code that implements a feature on web browsers that do not support the feature. Most often, it refers to a JavaScript library that implements an HTML5 web standard, either an established standard (supported by some browsers) on older browsers, or a proposed standard (not supported by any browsers) on existing browsers

For example, if you wrote a polyfill for Array.prototype.map (or, to take a newer example, for Array.prototype.flatMap) which was perfectly in line with the official Stage 4 specification, and then ran code that defined Array.prototype.flatMap on browsers which didn't have it already:

if (!Array.prototype.flatMap) {
  Array.prototype.flatMap = function(...
    // ...
  }
}

If your implementation is correct, this is perfectly fine, and is very commonly done all over the web so that obsolete browsers can understand newer methods. polyfill.io is a common service for this sort of thing.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • So... people going against best practices way back in the day is the reason why I keep accidentally writing `.contains()` and then have to take a second to realize it's `.includes()` in JS. Nice. – DexieTheSheep Jul 06 '23 at 17:00
4

As long as the changes are isolated to your systems (e.g. not part of a software package you release for distribution) is there a good reason not to take advantage of this language feature?

As a lone developer on an isolated problem there are no issues with extending or altering native objects. Also on larger projects this is a team choice that should be made.

Personally I dislike having native objects in javascript altered but it's a common practice and it's a valid choice to make. If your going to write a library or code that is meant to be used by other's I would heavily avoid it.

It is however a valid design choice to allow the user to set a config flag which states please overwrite native objects with your convenience methods because there's so convenient.

To illustrate a JavaScript specific pitfall.

Array.protoype.map = function map() { ... };

var a = [2];
for (var k in a) {
    console.log(a[k]);
} 
// 2, function map() { ... }

This issue can be avoided by using ES5 which allows you to inject non-enumerable properties into an object.

This is mainly a high level design choice and everyone needs to be aware / agreeing on this.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • `for...in` with Array expression – user422039 Apr 21 '11 at 09:38
  • 2
    @user422039 Yes it's an examplar pitfall. Whether it's good practice to call `for in` on array is a different issue. – Raynos Apr 21 '11 at 09:41
  • 3
    no, here using property enumeration to walk array is the major problem – user422039 Apr 21 '11 at 09:51
  • @user422039 Again it's an example of how your code can _break_ when you use monkey patching. Personally I would say it's valid to use property enumeration to walk an `Object`. and an `Array` is an `Object`. A generic function _should_ not have to shield against subtypes of `Object` – Raynos Apr 21 '11 at 10:09
4

It's perfectly reasonable to use "monkey patching" to correct a specific, known problem where the alternative would be to wait for a patch to fix it. That means temporarily taking on responsibility for fixing something until there's a "proper", formally released fix that you can deploy.

A considered opinion by Gilad Bracha on Monkey Patching: http://gbracha.blogspot.com/2008/03/monkey-patching.html

Dafydd Rees
  • 6,941
  • 3
  • 39
  • 48
2

The conditions your describe -- adding (not changing) existing behavior, and not releasing your code to the outside world -- seem relatively safe. Problems could come up, however, if the next version of Ruby or JavaScript or Rails changes their API. For example, what if some future version of jQuery checks to see if Array.map is already defined, and assumes it's the EMCA5Script version of map when in actuality it's your monkey-patch?

Similarly, what if you define "sum" in Ruby, and one day you decide you want to use that ruby code in Rails or add the Active Support gem to your project. Active Support also defines a sum method (on Enumerable), so there's a clash.

Chris Oei
  • 136
  • 1
  • 4
  • 1
    "what if some future version of jQuery checks to see if Array.map is already defined, and assumes it's the EMCA5Script version of map" Then it made an unjustified assumption and deserves what it gets. – ChrisJJ Dec 08 '16 at 00:56