Code Fragility.. and remedies
Building software that can be easily maintained and extended without breaking the existing is not effortless. If we are able to slow down in the initial part of our solution and think about what actually we are designing, we might have more chances to avoid some of the common pitfalls in solution implementation: Imperative code that branch a lot, based on functional requirements. We all need to take in account that software change. For this reason, we need to be open for extension but not modification, and build solutions that are easier to maintan and extend. Otherwise will end up like someone trying to extend a sandcastle.
If is full moon or is half moon , or if the sky is cloudy or the Sea is calm, I ll feed 53.2 grams of dog food to my Guinea Pig, Rudolf.
This seems more a formula for a spell than something meaningful. With time your Acceptance Criteria will disappear in the abbyss of Jira or Confluence and the only point of truth will be this ambiguous bunch of conditions. Can we agree that is dangerous? Let’s translate this odd example into a more realistic one, something I have seen many times unfortunately:
The code above might actually work, but what is it actually doing ? What is the System Under Test (SUT)? The code is very fragile. I could easily break the Open Close Principle ,never mind the Cyclomatic complexity…
Never heard about Thomas McCabe, has been the first Engineer that decide to give a name to such ungodly code, that can be easily described via graphs and a formula: M= E-N +2P
- N = Number of nodes of the graph
- E = The number of edges in a Graph.
- P = The number of connected components
Alternatives
There are few alternatives, in order to write code a bit more robust, easier to maintain and can be more easily represent the domain and its bounded context. If we look into the example above, we could approach in 3 possible ways :
Dividi et Impera. Why do not we start encapsulating what changes and use some level of Abstraction ? We could create an interface that represent a generic operation for each IF condition and isolate the domain responsabilities.
To make things a bit more transparent I will need a class that would handle the initial setup:
We used enumeration to rationalise the codes. Created at line 19 a Map that will contain a handler for each code operation (IF). For example, to solve a call about BB codes kind, we will have a related class that will handle and encapsulate the related logic:
The client would do a transparent call like this:
Does this code look familiar somehow? I think resemble very much a classic Command Pattern.
Obviously the use of an instance in the static map can be easily replaced with a more functional way of work and use anonymous lambdas or any Functional Interface that will fit your purpose. For example we could transform the OO command pattern in a more functional one:
From Java 17, we can use finally Switch expression and pattern match, something that can help write more concise code in a more functional way compromising with code branching:
Conclusions
As we can see, the temptation to write a couple of ifs and solve our domain problem can lead in time with more problems. I know we should try to follow a KISS principle but I personally believe that not all the cases are the same and when we know that something can easily change or get extended after a few interactions, we should try to give the code a better organisation. As well have in your pipelines some code static analysis like Sonarqube could help to make these decisions earlier at the development stage.