Essential Developer Principles #4 – Open Closed Principle
The “Open Closed Principle” is usually summarised as code should be “open to extension” but “closed to modification”. The way I often express it is that when I am adding a new feature to an application, I want to as much as possible be writing new code, rather than changing existing code.
However, I noticed there has been some pushback on this concept from none other than the legendary Jon Skeet. His objection seems to be based on the understanding that OCP dictates that you should never change existing code. And I agree; that would be ridiculous. It would encourage an approach to writing code where you added extensibility points at every conceivable juncture – all methods virtual, events firing before and after everything, XML configuration allowing any class to be swapped out, etc, etc. Clearly this would lead to code so flexible that no one could work out what it was supposed to do. It would also violate another well-established principle – YAGNI (You ain’t gonna need it). I (usually) don’t know in advance in what way I’ll need to extend my system, so why complicate matters by adding numerous extensibility points that will never be used (or more likely, still need to be modified before they can be used)?
So in a nutshell, here’s my take on OCP. When I’m writing the initial version of my code, I simply focus on writing clean maintainable code, and don’t add extensibility points unless I know for sure they are needed for an upcoming feature. (so yes, I write code that doesn’t yet adhere to OCP).
But when I add a new feature that requires a modification to that original code, instead of just sticking all the new code in there alongside the old, I refactor the original class to add the extensibility points I need. Then the new feature can be added in an isolated way, without adding additional responsibilities to the original class. The benefit of this approach is that you get extensibility points that are actually useful (because they are being used), and they are more likely to enable further new features in the future.
OCP encourages you to make your classes extensible, but doesn’t stipulate how you do so. Here’s some of the most common techniques:
- Pass dependencies as interfaces into your class allowing callers to provide their own implementations
- Add events to your class to allow people to hook in and insert steps into the process
- Make your class suitable as a base class with appropriate virtual methods and protected fields
- Create a “plug-in” architecture which can discover plugins using reflection or configuration files
It is clear that OCP is in fact very closely related to SRP (Single Responsibility Principle). Violations of OCP result in violations of SRP. If you can’t extend the class from outside, you will end up sticking more and more code inside the class, resulting in an ever-growing list of responsibilities.
In summary, for me OCP shouldn’t mean you’re not allowed to change any code after writing it. Rather, it’s about how you change it when a new feature comes along. First, refactor to make it extensible, then extend it. Or to put it another way that I’ve said before on this blog, “the only real reasons to change the existing code are to fix bugs, and to make it more extensible”.
The way I've come to think of the SOLID Principles is that they provide a vocabulary for particular code smells, which effectively point to the introduction of technical debt. I've not read Skeet's opinion but I agree with him. In the beginning, code *can't* be OCP compliant because we don't know what extension points are needed, because we don't know how, where or when the code will be subject to change.Joshua Lewis
What's important to realise about the SOLID Principles is that one cannot prove that a design is SOLID compliant, because for any design, I can introduce a change that will introduce a violation to one or more of the SOLID Principles. And this is their power. SOLID violations are telling us that the design we've used until now doesn't cater for the current change. One or more of the assumptions we made (about how and where the system will change) when we came up with the design has just been shown to be invalid. This is very useful information: do we 'remodel' and redesign the code so that it is 'SOLID compliant', and decide not to take on Technical Debt, or do we decide not to remodel now, live with the SOLID violaton, and take on some (deliberate and prudent) Technical Debt?