Refactoring: Guard Clauses
A technique to be a better developer
“In computer programming, a guard is a boolean expression that must evaluate to true if the program execution is to continue in the branch in question. Regardless of which programming language is used, guard code or a guard clause is a check of integrity preconditions used to avoid errors during execution.” — Wikipedia
The main problems that appear in code in which the guard clauses technique is not applied are the following:
- Excessive indentation — Excessive use of the control structure if nested means that there is a high level of indentation that makes code reading difficult.
- Relationship between if-else — When there is a large number of separate code fragments between if-else, which are conceptually related to each other, it’s necessary to perform the code reading by jumping between the different parts.
- Mental effort— A consequence of the different jumps in the source code causes an extra effort to be generated in the generation of code.
The practical application of a guard clause is the following case:
In this case, and most of the time, you must reverse the logic to avoid using the reserved word
else. The previous code would be rewritten as follows:
Therefore, the particular cases that cause an exit of the method would be placed at the beginning of the method and act as guards in a way that avoids continuing through the satisfactory flow of the method.
In this way, the method is easy to read since the particular cases are at the beginning of the same and the case of satisfactory flow use is the body of the method.
There are detractors of the guard clauses who indicate that there should only be a single exit point in each method and with this technique, we find several exit points. It should not be confused with having return everywhere and without control in our methods that will make us have even greater mental effort. But all the returns are clearly controlled since they will be found in the guards or at the end of the method.
Below we will see examples of more complex guard clauses in which the reading and understanding of the code are considerably improved.
Imagine that you have to create a method that calculates the cost of the health insurance in which the userID is received as a parameter.
A search in a database is done using this ID to retrieve a user. If the user does not exist, an exception called
UserNotFoundException will be thrown. If the user exists in the system, the next step is to verify that the user's health insurance corresponds to one of those that are valid for this algorithm: Allianz or AXA. If the insurance is not valid, an exception called
UserInsuranceNotFoundException must be returned. Finally, this algorithm is only valid for users who are of Spanish nationality. Therefore, you should check again if the user is Spanish to perform the insurance calculation or return an exception called
As you can see, the code has many levels of indentation. The same version of the previous algorithm is shown below, but the guard clauses technique has been applied. This technique allows the code to be more readable. Note that three guard clauses have been applied that allow generating alternative paths (throw exceptions) that do not interfere in the algorithm result.
Some questions that must be resolved:
- Why are there not cases of
- Stop thinking! If your code requires cases like
else if, it’s because you are breaking the Principle of Single Responsibility and the code makes higher-level decisions, which should be refactored using techniques such as division into submethods or design patterns such as command or strategy.
- Negative conditions are not well understood.
- For this, we have another refactoring technique called extract method, which consists of extracting code into functions for reuse or for reading comprehension. In the following example, we modified the previous example to create methods that allow better reading and understanding of the code.
In the use of a clause guard, the logic of the conditions is normally inverted and, depending on the complexity of the condition, it is quite complex to understand what is being evaluated in that condition.
That is why it’s good practice to extract the logic of the conditions in small functions that allow greater readability of the code (and, of course, to find bugs in them) since the responsibility of evaluating the condition is being delegated to a specific function.
For our example of medical insurance we can generate the following methods:
It is not necessary to create a function to check if the user exists since just checking that the user is different from null or undefined is sufficient. Therefore, the resulting code would be the following:
Summary and Resources
There are many practices to improve the quality of the code. The most important thing to learn when applying refactoring techniques is that they should be focused on two points, mainly:
- Uncouple the code — This allows small changes that do not cause large chained changes throughout the software project.
- Readability — It’s very important that developers understand that most of the time of their work is based on reading code, and probably code written by another developer. It is very beneficial in cost/development that a developer does not spend time understanding elementary logic because it is not easy to read.
Refactoring starts from the most elementary point, a simple if, to an architecture pattern. It is important to take care of all aspects of our software development.