I know that many people prefer to have guard conditions rather than nested "if" statements in their code. I'm going to slightly disagree with this preference - since it is not always the best approach.
Example of method with nested "if" statements:
public static void NestedIfs(dynamic obj) { if (obj.isShape()) { if (obj.isRectangle()) { if (obj.isSquare()) { obj.printSideLength(); } else { obj.drawDiagonal(); } } obj.Draw(); } else { obj.printFullName(); } }
This same method could be rewritten by using guard conditions, as follows:
public void GuardClauses(dynamic obj) { if (!obj.isShape()) { obj.printFullName(); return; } if (obj.isRectangle() && obj.isSquare()) { obj.printSideLength(); } if (obj.isRectangle() && !obj.isSquare()) { obj.drawDiagonal(); } obj.Draw(); }
Now, having looked at the above code snippets, let's see why you would choose one or the other.
In many cases, code written using nested conditions, closely follows the initial, visualized logic, which can be preferable. To explain this point better, let's present the above code using an activity diagram.
As you can see, the nested "if" statements seem to be taken straight from the diagram, by just converting words into programming statements. If you are used to think about the method's logic this way, you will most likely use nested conditions.
Another good reason to use nested conditions is to keep the code easily maintainable, in case more changes are expected to happen. When people think about code refactoring, they usually end up with guard conditions, thinking that this is the result of refactoring. I agree with this point if this is the last refactoring for this code (future changes are supported through extensibility instead of direct modifications - due to open/closed principle). However, if the given method is going to be changed often, the more compact version with guard conditions might cause more problems or difficulties in this extent.
Problem is that developers tend to think about each "if" condition as an isolated block that can be moved up and down inside the method, without affecting the functionality. However, if we move guard conditions, the output of the method changes, because there are several invisible (implicit) "else" statements between our guard clauses:
public void GuardClauses(dynamic obj) { if (!obj.isShape()) { obj.printFullName(); return; } else { if (obj.isRectangle() && obj.isSquare()) { obj.printSideLength(); } else if (obj.isRectangle() && !obj.isSquare()) { obj.drawDiagonal(); } obj.Draw(); } }
Because of these "else" statements (primarily because they are invisible), developers will need to think and brainstorm more before making any change to the code. In case of the nested conditions, locating the needed point of change is a matter of chasing down the graph of "if" statements, since all "else" statements are explicitly visible.
Obvious advantages of using guard conditions over nested "if" statements include writing final version of the code, and optimizing the look of the method block.
In these cases, we have decided that the method will not be changed often, or we can afford the cost of complex changes; each change will require the developer to do "reverse-engineering" mentally, and then only to apply the actual changes.
As a conclusion, your refactoring today should not complicate your refactoring tomorrow. I encourage you to base your decisions on your actual needs and plans for the code that you are delivering, instead of preferring guard conditions without real need.
Refactoring skills can be greatly improved by knowing design patterns, software architecture, and Test-First techniques (including Extreme Programming). Make sure to read other articles on the subject or consider taking one of my training courses for groups.
The author of the above content is Tengiz Tutisani.
If you agree with the provided thoughts and want to learn more, here are a couple of suggestions: