TDD and the SOLID principles (Part 3 – OCP)
Table of contents:
Part1 – Indrocution
Part 2 – SRP
Part 3 – OCP (this post)
Part 4 – LSP
Part 5 – ISP
Part 6 – DIP
Part 7 – Conclusion
The Open/Close Principle
The Open/Close Principle states that:
SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICTION.
This statement pretty much says it all, and naturally extends the Single-Responsibility principle: Classes (and functions, etc.) should be kept as simple as possible (having a single responsibility) and therefore should never have to change. Therefore, if you want to add functionality to your system you should add new code instead of changing existing one. This leads to the obvious conclusion that your classes should be designed to be extensible.
Here are a few indicators whether your code adheres to the OPC or not:
Adheres to OPC
· A base class that contains common functionality, with derived classes that contain the variable functionality
· A class that delegates anything that is not part of its core functionality to other objects though an interface (composition)
· Many switch-case statements
· Many if statements
· Sequential blocks of code that handle loosely related functionality
· Complicated logic that is not inherent from the problem domain (a.k.a. Accidental complexity vs. Essential complexity)
These indicators are only “rules of thumb” and not strong rules. There can be exceptions and you should consider your code on a case by case basis. However, you’ll probably notice that clean code that was written using TDD would contain much less switch statements and even if statements, and will use composition instead almost everywhere.
OCP and TDD
As with the Single Responsibility Principle, TDD leads you to do the right thing. If you were writing your code without TDD, you’d likely change existing code in order to add functionality. However, when you would try to write unit tests for it afterwards, you would realize that the test needs to be complicated, because it needs to prepare a lot of preconditions that need to satisfy the basic (old) functionality before you can test the added functionality.
On the other hand, if you would write the unit test first (as you would in TDD), you’d probably notice that writing the test as an added functionality to the existing class becomes too complex, and you’d prefer to introduce a new class which extends the old one, or use composition.
In addition, if you would not use TDD, you’ll probably have to change existing tests in order to support the new functionality. Beside of being more useless work, this is dangerous, because while you change existing tests after the code already works, there’s a risk that you’ll introduce bugs into the tests without noticing (the tests would pass even when they should fail).
OCP and Backward compatibility
If you stick to the OCP (and the SOLID principles in general), you’ll end up with a strong code base that rarely changes, and easily extended. This means that any clients you may already have won’t need to change their existing code in order to enjoy the new functionality. In other words, it’s simpler to keep backward compatibility this way.
Another beneficial outcome of this is that you can release often without fearing that you break existing clients. And clearly, releasing more often means shorter feedback-loop, more business value and happier customers!