Replacing nested if-statements with guard clauses

In this post, let’s look at why replacing nested conditionals (e.g. nested if-statements and else-statements) with guard clauses can help improve the readability and simplicity of the code you write.

Improving the simplicity of the code you write is important for a number of reasons. For example:

  1. Makes code more “linear”
  2. Easier to understand for other developers (e.g. lower development costs)
  3. Bugs are more likely to be spotted during the development process
  4. Etc.

One of the most common criticisms of using guard clauses is that it causes your functions to have multiple exit points, which many developers regard as a no-no. On the other hand, the goal is to avoid the conditional from hell (see below).

$result = false;
if () {
    if () {
        if () {
            if () {
                if () {
                    ...
                }
                else {
 
                }
            }
            else {
 
            }
        }
        ...
    }
    else {
        ...
    }
}
return $result;

As with anything in life, there are often important tradeoffs to consider when deciding between two competing approaches to doing something. Early in my development career I made heavy use of nested conditionals to ensure that my functions only had one exit point.

As time went on, I did a complete 180 turn and I now greatly greatly prefer guard clauses for the reasons I mentioned above. I’ve found that it makes my code much easier to debug and I feel that it has significantly cut down my development time.

Here’s a short example (PHP/Laravel) of how a nested conditional might be re-written using guard clauses to achieve the exact same result:

Nested Conditionals (single exit point)

public function getUser($id){
    $response = [];
    if (is_numeric($id)) {
        $user  = User::find($id);
        if (isset($user->id)) {
            $response = ["status"=> "success", "data"=> $user];
        }
        else {
            $response = ["status"=> "error", "data"=> "invalid user id"];
        }
    }
    else {
        $response = ["status"=> "error", "data"=> "id must be numeric"];
    }
    return response()->json($response);
}

Guard Clauses (multiple exit points)

public function getUser($id){
    if (!is_numeric($id)) {
        return response()->json(["status"=> "error", "data"=> "id must be numeric"]);
    }
    $user  = User::find($id);
    if (!isset($user->id)) {
        return response()->json(["status"=> "error", "data"=> "invalid user id"]);
    }
    return response()->json(["status"=> "success", "data"=> $user]);
}

The common tactic is to invert if-statments to guard against the negative outcomes. Although the nested example above was only a few levels deep, it’s not difficult to imagine one that is far more complex.

In conclusion, I know that each developer has their own style and coding preferences. Often times there is no real “right” or “wrong” way to code something. In my opinion, guard clauses provide a simpler and cleaner way to structure code as a rule of thumb — despite the fact that they produce multiple exit points.

Note: A quick peak at the source code of many of the most popular web frameworks point to the fact that a lot of talented developers seem to prefer guard clauses as well :)

comments powered by Disqus