11

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?

8

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.

  • You certainly shouldn't think as any principle as a design goal. They are tools - you're not making the software to be pretty and theoretically correct on the insides, you're trying to produce value for your client. It is a guideline, nothing more. – T. Sar May 24 '17 at 13:46
  • @T.Sar A principle is a guideline, something you strive for, they are oriented at maintainability and scalability. That looks like a design goal to me. I cannot see a principle as a tool in the way I see a design patttern or a framework as a tool. – Tulains Córdova Jul 12 '17 at 12:28
  • @TulainsCórdova Maintainability, Performance, Correctness, Scalability - those are goals. Open-Closed principle is a means towards them - just one of many. You don't need to push something towards the open-closed principle if it isn't applicable to it or it would detract from the actual goals of the project. You don't sell "Open-closedness" to a client. As a mere guideline, it is no better than a rule of thumb that can be discarded if you end up finding a way to do your thing in a more readable and clear way. Guidelines are tools, after all, nothing more. – T. Sar Jul 12 '17 at 12:50
  • @T.Sar There are so many things that you can't sell to a client... In the other hand, I agree with you in that one must not do things that detract from the goals of the project. – Tulains Córdova Jul 12 '17 at 13:07
8

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.

  • 2
    "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
  • 1
    Here is CodeReview answer that illustrates open for extension . That class design is extendable. In contrast, adding a method is modifying the class. – radarbob Dec 1 '16 at 16:47
  • Adding new methods violate LSP, not OCP. – Tulains Córdova Jul 12 '17 at 12:19
  • 1
    Adding new methods does not violate LSP. If you add a method, you’ve introduced a new interface @TulainsCórdova – RubberDuck Jun 21 '18 at 15:29
6

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.

  • 1
    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
  • You must think that the class will not only be used in your codebase. The library you write can be used in another projects. So OCP is important. Also, a new programmer not knowing of an extending class with the functionality he/she needs is a communication/documentation problem, not a design problem. – Tulains Córdova Jul 12 '17 at 12:23
  • @TulainsCórdova in application code this isn't relevant. For library code I'd argue semantic versioning was a better fit for communicating breaking changes. – opsb Jul 13 '17 at 11:09
  • 1
    @TulainsCórdova with library code API stability is far more important because it isn't possible to test the client code. With application code your test coverage will inform you of any breakages immediately. Put another way, application code is able to make breaking changes with no risk whereas library code must manage the risk by maintaining a stable API and signalling breakages using e.g. semantic versioning – opsb Oct 11 '17 at 16:20
6

In layman's words:

A. O/C principle means that specialization must be done by extending, not by modifying a class to accommodate for specialized needs.

B. Adding missing ( not specialized ) functionality means the design was not complete and you have to add it to the base class, obviously 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 some time in production:

  • There should be very little reasons to do so ( point B ), tending to zero over time.
  • ( Point C ) will always be possible although more infrequent.
  • All new functionality is supposed to be a specialization, meaning the classes must be extended (inherited from) ( point A ).
  • Open/closed principle is very much misunderstood. Your points A and B get that exactly right. – gnasher729 Jul 6 '17 at 10:36
1

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.

0

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.

  • 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
-1

As per my understanding - if you add new methods to the existing class then it won't break the OCP. however i m bit confused with the addition of new variables in the Class. But if you change the existing method and parameters in the existing method then it will surely break the OCP, because the code is already tested and passed if we intentionally change the method [When requirement change] then it will be problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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