Nested if-else statements can make it unnecessarily complicated to reason about the different executions paths and results of a function. A loss of productivity and the introduction of bugs due to misunderstandings can be the outcome.
The refactoring "Replace Nested Conditional with Guard Clauses" from Martin Fowler's book "Refactoring - Improving the Design of Existing Code (2nd Edition)" can help in those situations.
A guard clause checks for a condition and returns from the function if the condition is true, potentially doing some computation and returning a result. It makes it easier to reason about the function by ending one execution path early.
Here is an example function (from Replace Nested Conditional with Guard Clauses, Martin Fowler) before refactoring:
function getPayAmount() {
let result;
if (isDead)
result = deadAmount();
else {
if (isSeparated)
result = separatedAmount();
else {
if (isRetired)
result = retiredAmount();
else
result = normalPayAmount();
}
}
return result;
}
In this example, three nested if-else statements check different conditions (e.g., isSeparated
) and update a variable result
. The variable is returned at the end of the function.
The goal is to refactor the function into the following version:
function getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}
The refactored function uses guard clauses instead of nested if-else statements. The variable result
is not necessary anymore and has been removed. The refactored code is easier to understand because there is less state (no variable) and each execution path returns early.
How can the original function be refactored step-by-step?
You can simplify the original code by applying two refactorings to each condition:
- Inline return converts a variable assignment to a return statement. Such a conversion is possible when the following statement after the assignment in the control flow returns the variable.
-
Convert if-else to guard clause changes an if-else statement with
return
statements inside into to guard clauses, removing the indentation around the else block.
You can carry out these steps manually or with automation. The P42 JavaScript Assistant for VS Code is a refactoring tool that supports both operations. P42 indicates improvement opportunities with blue squiggly underlines. With refactoring automation, the code can be improved in a matter of seconds. After a final formatting step, the function has been refactored into the simplified version.
Here is how refactoring the code looks like in VS Code with P42:
๐กย ย This blog post is showing P42 JavaScript Assistant v1.29
Refactoring is essential for maintaining a healthy codebase, and many small improvements go a long way. By introducing guard clauses, you can often simplify nested if-else statements and make your codebase a little bit better.
Happy refactoring!
Top comments (18)
While I like the object construct and use it in many cases where the values are constant, in this particular case here, it would change the runtime behavior and could lead to unnecessary overhead and bugs. With it, all three functions
deadAmount()
,separatedAmount()
, andretiredAmount()
would be called regardless of the actual value ofresult
. Those computations have overhead, could error out, and could have side effects.Also, in cases where the values are constant and using an object lookup would work, using
??
instead of||
for the default value would be even better. If the result for the case is0
,||
would trigger because0
isfalsy
andnormalPayAmount()
would be returned, which could be a bug. With??
this would not happen.Small bonus: this variant would work without the downsides. However, I think it's harder to understand than the approach with guard clauses / early returns:
Using ?? or || depends on expected behaviour.
Your argument could be also the another way. So which is better to use depends on expected behavior
In the original code it was function calls.
??
does not change the behavior in this case,||
does if the function returns 0. I'm assuming that the functions return an amount tho, so point taken.if we look to the original code then my assumption isDead, isSeparated & isRetired are booleans.
So in the solution provided as object result needs to be set.
a possible solution could be
Agreed. Overall this goes back to my point that an object lookup is not the best refactoring here, since there is a risk that it'll change behavior and introduce bugs. Plus it adds unnecessary cognitive complexity.
Btw, I'm a huge fan of using the object lookup pattern when there is a need for it, e.g. when there are many entries or when the entries need to be assembled. In this example tho I think simple if statements are way easier to understand.
Agree that if statements with return is easier in this case
I have to agree with the author, it's kinda hard to read. Early returns (or guards) are better IMHO as they are simple to write, read and understand.
Yes, is it a better refacto !
The term "guard clause" could be somewhat confusing here, because the typical guard clause is not intended to solve the problem of nested logic with different outcomes but to stop the execution of a function in case some condition is not met (so kind of a bouncer or an execution guard).
What you did here is pretty close to the so called "early return pattern" which looks similar and contains guard clauses, but actually solves a different problem and has a wider scope. To read more about early return: medium.com/swlh/return-early-patte...
I am aware of refactoring.com and Martin Fowler, still wanted to point out that in this specific case, most people will probably name the child "early return" instead of "guard".
Good point! I followed the terminology from Fowler's example, which uses guard clause.
great post!
Thank you :)
I was thinking i'm doing wrong but I'm doing it from initial stage. Thankuhh!
I like it :) just been reading more about the guard clause. Good job !
Thank you ๐
seeing variables that are declared outside, and then accessed inside the function, makes by brain explode