Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do Ruby's "Open Classes" break encapsulation?

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:

  1. First, does this, in fact, violate the OO principle of encapsulation?
  2. 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?
  3. Third, should I ever be "opening" classes in my code, for any reason?
  4. 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?

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

like image 311
Daisy Sophia Hollman Avatar asked Nov 15 '10 13:11

Daisy Sophia Hollman


People also ask

What are open classes in Ruby?

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.

What does encapsulation mean in Ruby?

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.

What is OOP in Ruby?

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.


2 Answers

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.

like image 152
Jörg W Mittag Avatar answered Sep 26 '22 18:09

Jörg W Mittag


  1. In some cases yes. If you follow the paradigm of one class is responsible for one job and one job only then uses of reopening classes will often (though not necessarily) break encapsulation. It seems that this however not the tradition in ruby. For example the Array class acts as a list, array and a stack so the stdlib doesn't seem to adhere to strict encapsulation either. Matter of taste I guess.
  2. I don't know of any way. Maybe someone else will come up with something.
  3. My opinion is that I'd avoid doing it if you're writing a library that others will use. If you're writing an application and the need comes (trivial example: you need to have a mean method for arrays of numbers - it's a choice between added readability and not monkeypatching) I'd go for it.
  4. The most (in)famous real world monkeypatcher are rails. So often it's good to document especially well changes to core classes. And yes testing helps.
like image 40
Jakub Hampl Avatar answered Sep 25 '22 18:09

Jakub Hampl