If we analyze many common mistakes, we will always find a single culprit.
The bloody coupling.
In previous articles, we talked about the definition of software design in an axiomatic way.
We enunciated the single rule to know what objects to represent in our model.
And we showed the only principle that we should use.
We are going to add the only mistake we should avoid by all means to this axiomatic list.
Global variables link a global reference from the code. This link cannot be easily broken unless we connect to interfaces instead of the classes themselves and use dependency inversion (the SOLID D).
Having global variables in a structured language involves being attached to a reference that cannot be replaced, mocked or deferred over time. In object-oriented programming using classification languages, the problem is the same.
This is a step back to more pure functional languages where there’s and explicit prohibition enforced by preventing functions from having side effects.
If we take an extreme and minimalist position:
Every function/method should only invoke objects references by their attributes and/or their parameters.
Those plugs that allow us to ‘configure’ the software using arbitrary global references from anywhere on the code.
They are a particular case of global references and prevent the correct unit test of a system. If something must be configurable, this possible configuration must be passed as an object as we proposed in this article.
In this way we can replace the configuration on tests and have full control and no side effects.
As we have described in the note on bijections, partially ignoring this principle implies taking the risk of losing information on the contract and making mistakes under different interpretations.
In our previous example we represented 10 meters with the number 10. In this case, we are coupled to the hidden assumption that 10 represents 10 meters.
Hidden assumptions appear at the worst moments of the development cycle.
This is a particular case of the above rule.
Null should never be used because it violates our only non-negotiable principle since it is not bijective with any real-world entity (Null only exists in the world of developers).
If we decide to use a null as a flag of some particular behavior we are coupling the decision of the function implementer (or set an attribute in null) to the one who invokes it (or accesses the attribute) and this ambiguous semantics brings innumerable problems.
The Singleton pattern is a controversial design pattern. If we look at it under the guidance of our single design rule we will discard its use immediately. An object is represented by a singleton if there is only one instance of its class.
This also violates the principle of being declarative since the uniqueness of a concept, in general, is coupled to implementation problems, so we are offending the only design rule that we imposed ourselves.
Besides, singletons are generally referenced through their class name, so we add all the problems mentioned in the first paragraph.
If/Case/Switch and all their friends
If clauses have a hidden coupling between the condition and the place where they are evaluated and violate the open/closed principle (The O on the solid acronym).
Ifs (and hence cases) should be avoided unless this conditions are business rules thus related to the bijection.
A business rule ‘A bonus should be paid to employees with 3 or more years on a company’ can be safely stated by an If clause but rules such as ‘If
the employee’s position is junior then pay them 10.000’ shouldn’t, since this is not essential on the business rule but accidental (related to the employee status) therefore should be treated with polymorphism.
In real world employees, are aware of their position but they are not usually aware of their age on the company.
If you need to add comments to your code there’s a smell you might not be declarative enough.
Code documentation is many times not synced with the code itself. Many times developers change the code and don’t have enough courage to change the code documentation bound to the code. This is another subtle coupling case.
Some months later we read the code and the documentation and need a lot of time to figure out their meaning.
If we are faithful to our unique design rule and have a declarative model, we will expect, consequently, that a small change in the requirements will generate a small change in the model and so on. When this does not occur, the dreaded ripple effect is produced, turning the software unpredictable and full of potential errors that hinder its maintenance.
There are many ways to remove coupling once identified. In this article we will see concrete examples of coupling reduction:
Coupling is necessary because the objects must know each other to collaborate and be able to solve the problems raised in the simulation.
Finding out which binding is good versus which one is bad to avoid the wave effect requires a little experience and a lot of staying true to the rules defined in this article.
Part of the objective of this series of articles is to generate spaces for debate and discussion on software design.
We look forward to comments and suggestions on this article.