Ruby and the Open-Closed Principle


#1

For most of my OO career (C++ for a long time, and then C#) I believed
in the OCP very strongly. It’s supposedly the cornerstone of OOP,
right? I came across this article today which brought it mind again:
http://www.ddj.com/blog/architectblog/archives/2006/05/open_closed_pri_1.html?cid=GS_blog_arnon

But I remember my mind getting warped for a few hours when I was first
learning Ruby last year and came across Rails code that extended
existing classes so you could do this:

20.minutes.ago

which I thought was just awesome. Ruby lets me change anything I want
about any class, it seemed, and it was a strange thing to me.

Of course, I’m not worried about breaking existing code when it’s just
my own code, but I’m still violating the open closed principle by
changing the Time class, right? But I think this ability is one of the
things that makes Ruby so great!

So what gives? Of course the idea of not changing behavior that clients
depend on still holds - but that’s something different, the concept of
invariant intefaces.

So maybe I’ve misunderstood the OCP all along?

Or is just that we’ve come a long way since then?

Jeff


#2

I use both, judiciously, especially modifications of library classes.
Adding additional behavior is less risky than changing existing
behavior. That’s all I ever do, in practical terms. As always,
different solutions are valid for different contexts.

Changing existing behavior may break other code that uses the same
classes, of course, so I would never do these changes without thorough
testing. I also wouldn’t expect others to use any of my code that made
risky modifications.

My $0.02,

dean


#3

Hi –
`
On Tue, 6 Jun 2006, Dean W. wrote:

I use both, judiciously, especially modifications of library classes.
Adding additional behavior is less risky than changing existing
behavior. That’s all I ever do, in practical terms. As always,
different solutions are valid for different contexts.

The problem is that once you add behavior, it is existing, and someone
else might therefore change it. In other words, you might end up with
two versions of a method with the same name. Precisely because of its
openness, the language doesn’t know that something’s been added at a
particular time or in the expectation of non-conflict.

Changing existing behavior may break other code that uses the same
classes, of course, so I would never do these changes without thorough
testing. I also wouldn’t expect others to use any of my code that made
risky modifications.

It’s interesting that there are numerous libraries available that make
it possible to do block-scoped changes to core behavior, but no one
ever seems to use them. This makes me wonder whether the demand for
selector namespaces, or some other language-level solution, is really
all that great.

David


#4

Jeff C. wrote:

For most of my OO career (C++ for a long time, and then C#) I believed
in the OCP very strongly. It’s supposedly the cornerstone of OOP,
right? I came across this article today which brought it mind again:
http://www.ddj.com/blog/architectblog/archives/2006/05/open_closed_pri_1.html?cid=GS_blog_arnon

But I remember my mind getting warped for a few hours when I was first
learning Ruby last year and came across Rails code that extended
existing classes so you could do this:

20.minutes.ago

which I thought was just awesome. Ruby lets me change anything I want
about any class, it seemed, and it was a strange thing to me.

Of course, I’m not worried about breaking existing code when it’s just
my own code, but I’m still violating the open closed principle by
changing the Time class, right? But I think this ability is one of the
things that makes Ruby so great!

But isn’t this the essence of OCP? The ability to extend a class (open
for extensions) without modifying the original code? (which remains
closed).

– Jim W.


#5

I think Ruby has an important message for the programmer:
“You know what you want/need better than I do.”

Personally, I’m not not sure that the 20.minutes.ago type of extension
is a judicious use of Ruby’s power. It is certainly very convenient
though, and so far I haven’t seen it get in the way of anything.

We write programs, and the language helps us do that. It shouldn’t
get in our way. Features like opening classes exist, in my mind,
because at times they are the best solution for a given problem. It
is up to the programmer to use them wisely though.

I hate when languages put a condom on my code.

Pat


#6

The problem is that once you add behavior, it is existing, and someone
else might therefore change it. In other words, you might end up with
two versions of a method with the same name.

That’s why you want to write tests.


#7

David wrote:

It’s interesting that there are numerous libraries available that make
it possible to do block-scoped changes to core behavior, but no one
ever seems to use them. This makes me wonder whether the demand for
selector namespaces, or some other language-level solution, is really
all that great.

The problem with block-scoped behavior changes is that they are not
really blocked scoped. In other words, the changes are not limited to
the block in which they occur, but also to any code called by the block.
Although the change is limit in extent (i.e. the change has a beginning
and end point in time), the scope of the change is potentially
unlimited.

Rather selector namespaces should be lexically scoped. In other words,
selecting a particular selector namespace should apply to only those
method calls lexically in the same scope. Selector namespaces should
give the guarantee that when I write:

obj.some_method

I know exactly WHICH some_method gets calls (modulo normal OO method
overriding).

A short aside: Way back in the old days of Lisp, variables were
dynamically bound. That means whenever you referenced a non-local
variable name, Lisp would search the call stack for the most recent
definition of that variable. It was easy to implement but turned out to
be a bad idea in the long run. Today, modern Lisp use lexical binding
(with dynamic binding still available by explicit request).

Today’s (misname) block scoped behavior changes act like dynamic
binding. I hope that the coming selector namespaces will be more like
lexical binding.

– Jim W.


#8

On Jun 6, 2006, at 8:38 AM, removed_email_address@domain.invalid wrote:

That’s why you want to write tests.

Yes, but that’s not necessarily enough. You can’t test in advance
every scenario where someone might, later on, have written a new core
method with the same name as yours.

I think Codeblogger was trying to say that when someone does write
that function your test will either fail because your code no longer
does what the test intends, or it will pass, in which case the code
will respond accurately even with the new core method. So there’s no
problem.

Of course, this hinges on comprehensive tests.
-Mat


#9

Jim W. wrote:

But isn’t this the essence of OCP? The ability to extend a class (open
for extensions) without modifying the original code? (which remains
closed).

– Jim W.

Hi Jim,

Could be… but I guess I understood the essence of OCP to mean I can’t
modify the original behavior. If I decide to change the definition of
Time.now, I haven’t modified the original library code, but I’ve changed
the expected and well-documented behavior, which pragmatically is all
that really matters.

In other words, the “open” in OCP does seem to only talk about extending
classes; but in Ruby, since I can actually change behavior in classes
that would normally be “closed” in, say, C# or Java, then it seems that
the ability to open up an existing method and redefine it is,
essentially, not obeying the laws of OCP.

And hey, don’t get me wrong, I’m not saying that’s bad in any way - I’m
just trying to think through the consequences of a language like Ruby on
my old assumptions about what an OO language really is.

Jeff
softiesonrails.com


#10

Hi –

On Tue, 6 Jun 2006, Codeblogger wrote:

The problem is that once you add behavior, it is existing, and someone
else might therefore change it. In other words, you might end up with
two versions of a method with the same name.

That’s why you want to write tests.

Yes, but that’s not necessarily enough. You can’t test in advance
every scenario where someone might, later on, have written a new core
method with the same name as yours.

David


#11

2006/6/6, Mat S. removed_email_address@domain.invalid:

problem.
This won’t help if other code breaks because you redefined a core
method. Also, the pure test failure might not give you a good
indication where to look. Such bugs are hard to find and fix.

robert


#12

is a judicious use of Ruby’s power. It is certainly very convenient
though, and so far I haven’t seen it get in the way of anything.

We write programs, and the language helps us do that. It shouldn’t
get in our way. Features like opening classes exist, in my mind,
because at times they are the best solution for a given problem. It
is up to the programmer to use them wisely though.

I hate when languages put a condom on my code.

What does it make your code, then? :wink:


#13

On Jun 6, 2006, at 10:32 AM, Gennady B. wrote:

I hate when languages put a condom on my code.

What does it make your code, then? :wink:

Pregnant!


#14

Why would I break a core method to do something completely different?

If you want to improve a certain core class, I name it accordingly.
That’s the whole idea of “Domain Specific Languages”.


#15

Could be… but I guess I understood the essence of OCP to mean I
can’t modify the original behavior. If I decide to change the
definition of Time.now, I haven’t modified the original library
code, but I’ve changed the expected and well-documented behavior,
which pragmatically is all that really matters.

In other words, the “open” in OCP does seem to only talk about
extending classes; but in Ruby, since I can actually change behavior
in classes that would normally be “closed” in, say, C# or Java, then
it seems that the ability to open up an existing method and redefine
it is, essentially, not obeying the laws of OCP.

Jeff,

I would say that if in redefining a method you violate the original
contract of the method, including any preconditions, post conditions,
and class invariants, then you have violated the OCP. But if you’re
just extending the behavior of the method then you’re OK. If the
original unit tests for the class still pass, then assuming the tests
are adequate then I would say you haven’t violated OCP. To pick a
simple example, if I redefine a method to add logging and still call the
original method definition, then I have extended the method with extra
behavior (logging) but I haven’t changed or broken the original
behavior. And the original unit tests should still pass.

From this point of view the tests ensure the contract, not ‘final’
keywords or the like which are too coarse grained a mechanism as they
restrict legitimate extensions in addition to prohibiting modifications.
But of course we’re not usually in the habit of running the original
unit tests against redefined methods. In the absence of a run-time DBC
mechanism a la Eiffel that might not be a bad idea though. Hmmm…

Now in white-box unit tests I often intentionally violate OCP. I would
argue that redefining Time.now to return a configured time probably is
violating OCP. But in unit tests I sometimes do want to change the
behavior of classes not under test, to make things easier.

Steve


#16

Yes, you’re exactly right! I forgot that the most precise and
“conservative” meaning of the OCP is that you don’t modify the
original source code. That’s more important than the broader
restriction that you shouldn’t modify the class in any away, e.g., by
re-opening it the Ruby way. Certainly this should be done with great
“deliberation”. For example, you risk breaking libraries when they are
changed in subsequent releases, as others have remarked. However, I
don’t believe that re-opening classes violates the OCP, “a priori”.

Note that byte-code modification frameworks have become very popular
in the Java world, where they are used to “modify classes”, but in
very specific and controlled ways.

Thanks for the clarification on the OCP.

dean


#17

On 6/6/06, Gennady B. removed_email_address@domain.invalid wrote:

What does it make your code, then? :wink:

The only way I can interpret this is that if I don’t like language
restrictions, then I must be writing crappy code.

I’ve used plenty of languages where the designer built in some
mechanism to keep me from shooting myself in the foot. That’s
retarded though - sometimes we need more flexibility, some extra
power. The language designer certainly doesn’t know what I want to do
better than I do, so why should he place any restrictions on what I
can do?

This is a key difference between Ruby and Java. Java assumes that
you’re mostly stupid and tries to save you from yourself. Ruby
assumes you’re smart and lets you do what you want, for the most part.

Pat