Refactoring: How to avoid nested or several conditionals within a function?
Most of the time, having several conditionals inside a function and many levels of nested indentation make the code source so tedious to maintain, difficult to read and rapidly unclear — the pyramid of doom.
In the previous episode, we explored how to exit a loop without violating the Object Calisthenics rule: “Only One Level Of Indentation Per Method”! Through this article, we discuss two examples of nested IFs and try to explain, step by step, how to get rid of all these conditional structures.
Example 1: Introducing assertion function
Let’s dive into the first example and see how to change the code structure in order to remove all nested IFs:
For simplicity, I picked out a simple example with three nested IFs.
In our case, conditionals are very simple, but we can imagine more complicated ones. Yet, extracting conditions into variables could not be an appropriate solution and may introduce more tests than needed:
So to avoid duplicating or moving conditions into constants, I found a better approach by adding an assertion function:
This assertion is straightforward and in addition, it returns the result of isTrue.
If run() would return a boolean value which is not the case here, we could compact the code to fit in one line.
“when” could be static and not related directly to the correspondent class or file.
Once we introduce the assertion function, we replace all nested conditions as follows:
If we still need to keep constants when conditions are too complex, we can do the following:
Of course, I prefer the first refactoring, where the call of the assertion function is immediate and cleaner.
Example 2 — Replacing IFs with Math.max
Another elegant way to replace conditionals could be implemented by checking and returning the maximum of several results. For example, consider the following code:
Besides, the list could grow by adding other positions or coefficient ranges which lead to a more complex and unreadable code.
So, how could we eliminate all these IFs?
In this case, we can start extracting conditions to constants in order to use Typescript Accessors later:
SonarQube would emit a warning when we use more than three return statements. For this reason, I excluded this solution.
Now we have constants in place, we move them into accessors as follows:
Simultaneously, I added position and coefficient as class properties:
However, I would like to change all getters in order to return the number of days off instead of a boolean value:
Obviously, function name has been changed to reflect our purpose.
Now, since we have the number of days off over all getters, replacing conditionals with Math.max becomes straightforward:
Summary
As we can see, in both examples, our source code became more readable and cleaner. After all, our main goal is to write beautiful code — that works 😉— .
I didn’t mention unit tests during refactoring because it is beyond the scope of this article, however, unit tests are required and must accompany the whole process of refactoring.
The complete source code for example 2 could be found here: https://github.com/elie29/if-elseif.
Each step has its own dedicated branch.
Don’t hesitate to give me your feedback or contact me on Twitter @elie_nehme