July 31, 2021

My Take on SOLID (WIP)

S - Single-responsiblity Principle
A software entity (classes, modules, methods, functions, ...) should have only one responsibility, if you need to change a software entity for new requirements then this is a pointer towards adding new functionality and should be avoided. The new fucntionality (or responsibility) should be placed on another entity or a new one.
The aim is to lean towards small simple classes, avoid monster do everything classes at all costs. Smaller simpler classes have (at least) 3 advantages, they are simpler to code, simpler (especially for someone else) to understand, and simpler to test. This approach does have the disadvantage that a project becomes swamped with classes each with their own very specific responsibility and sometimes you cannot see the woods for the trees. For this reason, it is more important in a class definition to define the exact responsibility succinctly in the name of the class or where there is any possible doubt, expressed definitively in the class comments.

O - Open for extension/closed for changes
Code should be extended but not modified, especially released code. The trouble is that the more a piece of code changes (especially released code), the more likely bugs will be introduced and that existing tested code will require re-testing. Also this will likely break existing unit tests, or require new unit tests to validate the changes.
Very important when releasing a hotfix. During a hotfix you will break this rule but the trick is to do so in a minimal manner and where possible, change it in the next release so that the modification becomes an extension. Sometimes a hotfix unavoidably requires a lot of modifications in which case the hotfix will need to become an update.

Requires further comment: Surely if you take some code and extend it, say by adding a method to an existing class, you've also modified it.

L - Liskov substitution principle.
When you look at the definition of this it talks about being able to substitute instances of a base class with derived classes. However this was defined in the context of C++ where interfaces are not a language defined thing, you have to implement them as abstract base classes. I think that the definition should be redefined in terms of interfaces. If several classes implement an interface, then the behaviour of each interface implementation should be consistent. There should not be any returning of strange values on an interface member because in that instance that particular member does not really do anything or is not really appropriate. It should do what the client expects it to do.

I - Interfaces for client objects.
Expose your objects as interfaces or abstract classes for client objects to use, not as concrete objects. That way you can change the implementation without the client needing to change. Different client types should receive their own interface.

D - Depend on interfaces not concrete objects.
This follows on from the last principle, a client object should receive an interface when it needs to use an object, not the concrete object itself.

Requires further comment: This leads on to Dependency injection as a tool to help stick to this principle.
Requires further comment: What about Logging and Code Contract classes. They are ubiquitous. One work around is to use a static class for these, where the static class relays all calls onto an object interface.

No comments: