Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have recently being reading a web site about clean code development (I do not put a link here because it is not in English).

One of the principles advertised by this site is the Open Closed Principle: each software component should be open for extension and closed for modification. E.g., when we have implemented and tested a class, we should only modify it to fix bugs or to add new functionality (e.g. new methods that do not influence the existing ones). The existing functionality and implementation should not be changed.

I normally apply this principle by defining an interface I and a corresponding implementation class A. When class A has become stable (implemented and tested), I normally do not modify it too much (possibly, not at all), i.e.

  1. If new requirements arrive (e.g. performance, or a totally new implementation of the interface) that require big changes to the code, I write a new implementation B, and keep using A as long as B is not mature. When B is mature, all that is needed is to change how I is instantiated.
  2. If the new requirements suggest a change to the interface as well, I define a new interface I' and a new implementation A'. So I, A are frozen and remain the implementation for the production system as long as I' and A' are not stable enough to replace them.

So, in view of these observation, I was a bit surprised that the web page then suggested the use of complex refactorings, "... because it is not possible to write code directly in its final form."

Isn't there a contradiction / conflict between enforcing the Open / Closed Principle and suggesting the use of complex refactorings as a best practice? Or the idea here is that one can use complex refactorings during the development of a class A, but when that class has been tested successfully it should be frozen?

share|improve this question

6 Answers 6

The Open-Closed principle is more of an indicator of how well your software is designed; not a principle to follow literally. It is also a principle that helps to keep us from accidentally changing existing interfaces (classes & methods you call and how you expect them to work).

The goal is to write quality software. One of these qualities is extendability. This means it's easy to add, remove, change code with those changes tending to be limited to as few existing classes as practical. Adding new code is less risky than changing existing code so in this regard Open-Closed is a good thing to do. But what code are we talking about exactly? The crime of violating O-C is much less when you can add new methods to a class instead of needing to alter existing ones.

O-C is fractal. It apples at all depths of your design. Everyone assumes it is only applied at the class level. But it is equally applicable at the method level or at the assembly level.

Too frequent violation of O-C at the appropriate level is suggesting that maybe it's time to refactor. "Appropriate level" is a judgement call that has everything to do with your overall design.

Following Open-Closed literally means the number of classes will explode. You'll create (capital "I") Interfaces unnecessarily. You'll end up with bits of functionality spread across classes and you then have to write lots more code to wire it all together. At some point it will dawn on you that changing the original class would have been better.

share|improve this answer
    
"The crime of violating O-C is much less when you can add new methods to a class instead of needing to alter existing ones.": As far as I understand, adding new methods does not violate the O-C principle (open for extension) at all. The problem is changing existing methods that implement a well-defined interface and therefore have well-defined semantics already (closed for modification). In principle, refactoring does not change the semantics, so the only risk I can see is introducing bugs in already stable and well-tested code. –  Giorgio Oct 22 '12 at 9:23

The Open-Closed principle seems to be a principle that appeared before TDD was more prevalent. The idea being that it's risky to refactor code because you might break something so it's safer to leave existing code as is and simply add to it. In the absence of tests this makes sense. The downside to this approach is code atrophy. Each time you extend a class rather than refactoring it you end up with an extra layer. You're simply bolting code on top. Every time you bolt more code on you're increasing the chance of duplication. Imagine; there's a service in my codebase that I want to use, I find it doesn't have what I want so I create a new class to extend it and include my new functionality. Another developer comes along later and also wants to use the same service. Unfortunately, they don't realise that my extended version exists. They code against the original implementation but they also need one of the features that I coded. Instead of using my version they now also extend the implementation and add the new feature. Now we've got 3 classes, the original one and two new versions that have some duplicated functionality. Follow the open/closed principle and this duplication will continue to build up over the life time of the project leading to a needlessly complex code base.

With a well tested system there is no need to suffer this code atrophy, you can safely refactor code allowing your design to assimilate new requirements rather than having to continually bolt on new code. This style of development is called emergent design and leads to code bases that are able to stay in good shape over their entire lifetime rather than gradually collecting cruft.

share|improve this answer
    
I am not a proponent of the open-closed principle nor of TDD (in the sense that I did not invent them). What surprised me was that someone proposed the open-closed principle AND use of refactoring AND TDD at the same time. This seemed contradictory to me and so I was trying to figure out how to bring all these guidelines together into a coherent process. –  Giorgio Oct 19 '12 at 15:43
    
"The idea being that it's risky to refactor code because you might break something so it's safer to leave existing code as is and simply add to it.": Actually I do not see it in this way. The idea is rather to have small, self-contained units that you can replace or extend (thus allowing the software to evolve), but you should not touch each unit once it has been thoroughly tested. –  Giorgio Oct 19 '12 at 15:55

I think of the Open-Closed principle as a design goal. If you end up having to violate it, then that means your initial design failed, which is certainly possible, and even likely.

Refactoring means you're changing the design without changing the functionality. Likely you're changing your design because there's a problem with it. Perhaps the problem is that it's difficult to follow the open-closed principle when making modifications to the existing code, and you're trying to fix that.

You might be doing a refactoring to make it possible to implement your next feature without violating the OCP when you do it.

share|improve this answer

Refactoring, by definition, is changing code structure without changing behavior. So when you refactor, you don't add new features.

What you did as an example for the Open Close principle sounds OK. This principle is about extending existing code with new features.

However, don't get this answer wrong. I do not imply that you should only do features or only do refactoring for big chunks of data. The most common way of programming is doing a little bit of a feature than immediately doing a little bit of refactoring (combined with tests of course to make sure you did not change any behavior). Complex refactoring does not mean "big" refactoring, it means applying complicated and well thought refactoring techniques.

About the SOLID principles. They are really good guidelines for software development but they are no religious rules to be blindly followed. Sometimes, many times, after you add a second and third and n-th feature, you realize that your initial design, even if it respects Open-Close, it does not respect other principles or software requirements. There are points in the evolution of a design and of a software when more complex changes has to be done. The whole point is to find and realise these problems as soon as possible and apply refactoring techniques as well as possible.

There is no such thing as perfect design. There is no such design that can and should respect all existing principles or patterns. That is coding utopia.

I hope this answer helped you in your dilemma. Feel free to ask for clarifications if needed.

share|improve this answer
1  
"So when you refactor, you don't add new features.": but I could introduce bugs in a tested piece of software. –  Giorgio Oct 19 '12 at 11:37
    
"Sometimes, many times, after you add a second and third and n-th feature, you realize that your initial design, even if it respects Open-Close, it does not respect other principles or software requirements.": That's when I would start writing a new implementation B and, when that is ready, replace the old implementation A with the new implementation B (that's one use of interfaces). A's code can serve as a basis for B's code and then I can use refactoring on the B code during its development, but I think that the already tested A code should remain frozen. –  Giorgio Oct 19 '12 at 11:39
    
@Giorgio When you refactor you can introduce bugs, that's why you write tests (or even better do TDD). The safest way to refactor is to change code when you know it's working. You know this by having a set of tests that are passing. After you change your production code, the tests have to still pass, so you know you did not introduce a bug. And remember, tests are as important as production code, so you apply the same rule to them as to production code and keep them clean and refactor them periodically and frequently. –  Patkos Csaba Oct 19 '12 at 11:46
    
@Giorgio If code B is built upon code A as an evolution of A, than, when B is released, A should be removed and never used again. Clients formerly using A will just use B without knowing about the change, since interface I was not changed (maybe a little bit of Liskov Substitution Principle here? ... the L from SOLID) –  Patkos Csaba Oct 19 '12 at 11:48
    
Yes, this is what I had in mind: do not throw away working code until you have a valid (well-tested) replacement. –  Giorgio Oct 19 '12 at 15:40

To me, the Open-Closed Principle is a guideline, not a hard and fast rule.

With regard to the open part of the principle, final classes in Java and classes in C++ with all constructors declared as private violate the open part of the open-closed principle. There are good solid use cases (note: solid, not SOLID) for final classes. Designing for extensibility is important. However, this takes a good deal of foresight and effort, and you are always skirting the line of violating YAGNI (you ain't gonna need it) and injecting the code smell of speculative generality. Should key software components be open for extension? Yes. All? No. That in itself is speculative generality.

With regard to the closed part, when going from version 2.0 to 2.1 to 2.2 to 2.3 of some product, not modifying behavior is a good very idea. Users really don't like it when every minor release breaks their own code bade. However, along the way one often finds that the initial implementation in version 2.0 was fundamentally broken, or that external constraints that limited the initial design no longer apply. Do you grin and bear it and maintain that design in release 3.0, or do you make 3.0 non-backward compatible in some regard? Backward compatibility can be a huge constraint. Major release boundaries are the place where breaking backward compatibility is acceptable. You do need to beware that doing this might get your users upset. There has to be a good case for why this break with the past is needed.

share|improve this answer

In layman's words:

A. O/C principle means that especialization must be done by extending, not by modifying a class to acommodate for especialized needs.

B. Adding missing ( not especialized ) functionality means the design was not complete and you have to add it to the base class, oviously without violating the contract. I think this is not violating the principle.

C. Refactoring doesn't violate the principle.

When a design matures, say after sometime in production:

  • There should be very little reasons to do ( point B ), tending to zero over time.
  • ( point C ) will always be possible although more infrecuent.
  • All new functionality is supposed to be an specialization, meaning the classes must be extended (inherited from) ( point A ).
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.