Which principle was it? Open-closed or dependency inversion?

In my last blog entry I discussed how Test Driven Development (TDD) lead me to a greater understanding of the Open-Closed Principle (OCP). The general feedback from the post was very positive. However, several readers asked a question that I would like to answer. They pointed out that although I was discussing OCP, my example really displayed the Dependency Inversion Principle (DIP). Was my blog post misleading?

Just to recap, here is the example I used in the previous post:

   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 is a simple test for my “GetMembershipCommand” function. When I realized that I didn’t like the coupling created between my test function and the other production classes in my project, I altered the test to this:

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

The dependency inversion principle states:

High level modules should not depend upon low level modules. Both should depend upon abstractions.

In my example, I clearly used the dependency inversion principle. My GetMembershipListCommand no longer depends on the lower level Member and MembershipList classes - it only depends on the interface. But does that mean I missed the point of OCP? I don’t think so.

OCP is about the flexibility of your classes, not a specific implementation pattern. Can you construct a class for which the implementation can stay constant, even though you can extend the behavior? What might these techniques be?

  • Most examples I have seen focus on sub-classing the parent class and overriding methods
  • I claim that OCP is achieved in my example above by injecting classes with behaviors to the parent class
  • I have also built classes to which behaviors and extensions are passed directly as functions or command classes

No matter which of these techniques I use, I know one thing to be true: although the base class’ implementation has not changed, its behavior has been extended through these techniques.

In a recent podcast with Scott Hanselman, Robert Martin reviewed the SOLID principles. One point that Martin made is that the dependency inversion principle (DIP) is basically a restatement of OCP with "a 90 degree rotation".  OCP states the goal of not having to modify a class to extend behavior. DIP provides an implementation guideline that we should depend on abstract classes as much as possible.

I clearly used dependency injection to modify the behavior of the classes being tested. TDD showed me where I wasn't using techniques like dependency injection to easily and thoroughly test my objects. My effort resulted in classes that satisfy the open-closed principle.  It was very satisfying to see multiple principles converge on the single goal of clean code.

So what do you think? Is my definition of the open-closed principle on the mark or too liberal?

How TDD clarified the Open/Closed Principle for me

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.

Software Engineering Internships Available

I'm looking for two students in the St. Louis area for a part time internship here at Appistry. This position will report to me, Appistry's Chief Architect.  During the internship we will work on prototypes of distributed algorithms to use in upcoming releases of Appistry software.

Requirements:

  • Currently a senior or graduate student in Computer Science or related field
  • Able to work at least 15 hours a week
  • Familiar with C/C++, Java
  • Familiar with TCP/IP and network programming

If you are interested or have any questions about this position, please contact me at michael@appistry.com.

End of iteration demonstrations - The agile practice you probably aren't practicing

How often do you demonstrate your development builds to your customers during development of a release?  Unfortunately if you are like most agile teams that I encounter the answer is not very often, if ever.  If you are practicing agile software development you know about the importance of working in short increments.  But do you really extract all the value you can from your iteration?  Let me elaborate with a real life example I encountered recently.

About a month ago I traveled to a customer site to discuss EAF's future direction.  After the meetings, we headed back to the airport for the return flight.  I had been up since 4:00 A.M. and I couldn't wait to get home.  As I approached my gate, I learned that my plane had been delayed for maintenance issues.  As I had just made the travel plans several days earlier, I knew that another airline had flight back to my hometown and that I could probably get a seat on that flight.

This is where my frustration began and the inspiration for today's post. When I asked the gate attendant if the other flight had a seat available, here is what ensued:

Me: "I am pretty sure that American Flight 5455 is traveling to St. Louis in an hour.  Can I get on that flight?"

Gate Attendant: "Sure let me check my computer..." click click click click click click click click click....

Five minutes later

.... click click click click click click click click click ....

"... hmmm...  I forgot the sequence code.  Oh yeah 'next next list list'" (Don't ask me what that meant.)

....click click click click click click click click click ...

"... Why yes Mr. Groner, I can get you on that flight"

Not only was I tired and frustrated, you should have seen the impatient people behind me as they waited for my question to be answered. I kept thinking to myself... "This must be the most unfriendly user interface ever built.  Couldn't they have done something to make this smoother for the gate attendants?"

Every iteration should end with a demonstration to the customer

I'm sure that the attendant's reservation system was built years ago with non-agile methods, but just think what could have been built if a real gate attendant would have gotten a chance to see the system every two weeks as new functionality was added.  I'm sure early along the way an attendant would have said "You have got to be kidding me. I can't be typing for minutes just to get someone's flight switched.  You have to re-architect this interface."

Admittedly, my gate attendant example is a bit contrived, but the take away is the same, regular and often demonstrations from developers to the user base is an essential agile practice.

Consider the benefits:

  • Obviously, the immediate user feedback it essential to agile practices.  How else are you going to course correct your development efforts from iteration to iteration if the customer doesn't have a good sense of the work product as it grows?
  • During iteration planning, developers know they have to focus on complete features as they are going to have to demonstrate them in a short time.
  • Customers have the pressure of clearly articulating their user stories.  It's a very easy for developers to say "so really, in two weeks what are you expecting to see this application do?" 
  • In my opinion, demonstrations combat scope creep and pressure the team to get done on time.  Knowing that you will have to get up in front of your customers at an established time in the future will keep your focus on the task at hand.
  • Demonstrations promote the DONE=DONE mind set.  At almost every end of iteration demonstration I go to I hear at least one user say "Can I have this build now? It is useful to me already".  It's hard to accomplish that with half completed stories.
  • Finally, the demonstrations greatly improve communication paths and confidence between the business and engineering side of the house.  See my post on regular, small releases as I go into detail on this.

It's sad to me how many teams I encounter that consider themselves "agile" that don't follow through on this practice.  I find it essential and addictive.  If you don't do this in your organization try it, just once.  At the end of your next iteration plan establish a time for the demonstration with the customer.  Watch how it influences all members of the agile team.  You will be glad you did.

Categories:

Microsoft says you need to change how you are building your applications

Microsoft TechEd 2008 was held in Orlando, Florida the week of June 2nd.  I was fortunate to attend many of the technical sessions.  I was surprised how many speakers were conveying the same message: 

CPU speeds are topping out.  If you want your applications to run faster and better you are going to have to build your applications in a new way.  The solution isn't just to learn how to multi-thread your applications.  The solution lies in building your applications into smaller units of code called tasks that can be moved around to the different cores of a multi-core machine.

Even in Bill Gates' keynote speech he said

The... "need to take programs and break them down into parallel execution units now becomes absolutely necessary."

This statement was music to my ears.  Why?  It is the same model that we have been using for fabric computing since our beginnings in 2001.  Now we are starting to see this message more and more as cloud computing is gaining acceptance.  It's interesting that Appistry and Microsoft were driven to the same place for different reasons.   As I said, Microsoft's motivation is speed.  Developers have relied on faster and faster machines to accomplish more and more ambitious software projects.  Relying on faster machines to accomplish your goals is a thing of the past.  Only by decomposing problems and executing them in parallel will one gain application speed ups in the future.

When we at Appistry starting building EAF our motivation wasn't just application speed,  but also scale and reliability.  We were determined to build a framework that you could depend upon.  We advocate building your applications into smaller execution units (which coincidentally we also call tasks) and adding declarative information about how these tasks aggregate into your application.  With this information the a framework is able to make a decision on the best configuration and execution of your application across machines.  Whether it is a single dual core machine fabric or 500+ cores distributed around a data center, the framework is able to manage your system because it is written out of loosely coupled, well defined tasks.

A Challenge

So, here is my challenge to you as a developer.  Think about your most recently developed application.  Think about the code you wrote for your business logic and the code you wrote to handle the sequencing, threading, scaling, and reliability. Now, can you identify those business logic components and consider running them each, as logical standalone tasks, in a distributed framework?

Once you can accomplish that goal, you are on your way to building the "right" code for the future.  Applications that can be run on one core or many cores.  Applications that can scale to many machines with just simple configuration changes.  It's what we at Appistry have been advocating for some time, and now Microsoft is too.