How TDD clarified the Open/Closed Principle for me

by michael on January 22, 2009 · 5 comments

in Editorial

OpenClosedThe open/closed principle (OCP) (a software concept whose definition I could recite, but whose meaning I hadn’t internalized until recently) simply states:

Software entities should be open for extension, but closed for modification.

Correct application of the open/closed principle results in classes with high reusability, as the classes are built with extension in mind. OCP also provides consistency because established classes do not change after completion. Any developer will agree that reusability is a noble goal, but how do we know when we have achieved it?

I think OCP eluded me because of the illustrations I have seen in the past. Examples focused on depending on interfaces within a given class or function instead of a concrete class. When new behavior is needed, a separate class implemented to the same interface can be substituted to extend behavior. Factually, I knew this, but I was never certain that I applied OCP well. When I reviewed my older code I often felt that my classes were too tightly bound to single classes and had little reusability. The examples provided no metric to show when I was off course.

Fortunately for me two separate events came together that helped me understand what I had been missing. First, I listened to Scott Hanselman’s podcast with Robert Martin on SOLID principles. Martin did a great job illustrating each of the SOLID principles, so they were fresh in my mind. Second, as I was working on my latest project, I became frustrated with a unit test that I was constructing.

The test itself took more effort to construct than it should have. The setup phase of the test was long and complex. The test was fragile, as it depended on other classes in my project. To keep my tests clean I realized that my class should be able to correctly function with no dependence on other classes within my project. 

And that was it. That was the moment I fully understood the open/closed principle. A well designed class can be tested in isolation by injecting necessary behavior. The ability to inject behavior fulfills the “open” attribute of the open/close principle. Good unit testing practices led me to good class construction.

Let me go through a simple example test case to illustrate:

   1: testDefault(self):
   2:     member = Member("test member")
   3:     membership_list = MembershipList()
   4:     membership_list.add_member(member)
   5:     command = GetMembershipListCommand(membership_list)
   6:     result = command.execute()
   7:     self.assertEqual(["test member"], result)

This code tests my GetMembershipListCommand. (This is a simple example for illustration.) This class simply returns the contents of the membership list. Notice that I had to import two data structures outside my GetMembershipListCommand class to establish the test. The setup of  the test requires as many lines as the execution and validation sections of the test.

Knowing that I want my test cases to run isolated from the other classes, I constructed dummy/mock objects to provide behavior implementations to the class being tested. Converting to dummy/mock objects immediately showed me that my class was more coupled to lower-level classes than I had believed. I had to restructure my classes to meet the injected interface/behavior. Once completed, my base classes were much more cohesive and the overall set of classes become decoupled. 

In the end, the update was simple. My new test case looks like this:

   1: test Default(self):
   2:     command = GetMembershipListCommand(DummyList())
   3:     result = command.execute()
   4:     self.assertEqual(["test member"], result)

Instead of being dependent on the Member and MembershipList classes, my new implementation is only dependent on the interfaces I use. I am now able to remove any imports of classes outside my GetMembershipListCommand class and verify that my code is fully decoupled from other objects.

It doesn’t look that different, but it is much more flexible:

  • It is now easy to create a more straightforward test case – I can build specific dummy objects to meet my needs.
  • My classes are fully decoupled from other classes within the project and much more reusable.
  • My unit tests were much easier to read.
  • Test failures are isolated. A bug in a dependent class will no longer create failures in higher level classes.

So I finally got it. Application of the open/closed principle was achieved when I started injecting behaviors into my test cases. My higher-level classes no longer need to know about lower-level implementation details. The only thing they need to share is a common interface. It seems so simple now, but I hadn’t seen it before. TDD showed me the way. I now recognize that if my unit tests become complicated and contrived then OCP is probably being violated.

My code is cleaner, more easily supported and more modular all because of good application of SOLID principles that were achieved through good application of TDD.

{ 5 comments }

Victor noagbodji January 22, 2009 at 6:33 pm

Great article. It would be nice if you write some tutorials on TDD for the rest of us.

Robert 'Groby' Blum January 23, 2009 at 4:17 pm

I might be confused, but isn’t DummyList just a mock for your list?

Or do you mean you implemented DummyList so it only responds to the necessary methods? (In that case, I’m SOL – static typing… :)

Michael Groner January 23, 2009 at 5:47 pm

In my example the “DummyList” was a dummy object, not mock class. That way I was sure I had removed any dependencies on concrete classes. Python shielded me from having to build abstract interfaces with separate implementations but it would be necessary in C++ and Java.

I wanted to go into more code detail in the blog, but was concerned it was getting too long already. If there is some element of my post that you like to see in more detail in a later entry, please let me know.

- Michael

Brian Button January 27, 2009 at 10:21 am

Michael,

I’m glad you get these principles. I learned them from Bob in the very early ’90s and have held them very dear since. But in this case, where the act of injecting behavior and data into the classes being tested is the effect you’re seeing, does this seem more closely aligned with the DIP?

The SRP drives you down the road of creating small, coherent, but loosely coupled classes, while the DIP teaches you how to keep that coupling loose.

Admittedly, I usually think of the OCP from a statically typed language point of view, but even when viewed with an eye towards ruby, injecting behavior seems to favor the DIP.

What do you think?

– bab

michael January 29, 2009 at 1:25 pm

Brian -

Thanks for the question. It really got me thinking.

I hope you don’t mind, but my response grew into a blog post of its own. Please take a look and let me know what you think: http://bit.ly/rLtJ

- Michael

Comments on this entry are closed.

Previous post: With the RDBMS Ailing … What’s Next?

Next post: (Unintentional) Startup Humor – or – It Seemed Like a Great Idea