DEV Community

loading...
Cover image for The Power of Guard Clauses

The Power of Guard Clauses

codebryo profile image Roman Kuba ・3 min read

There are various practices to make code more readable and sometimes even faster. Readability is for me always the most important aspect.

Let's start with this code example:

function double(n) {
  if (typeof n === 'number') {
    return n * 2
  } else {
    return throw Error('double only takes numbers')
  }
}
Enter fullscreen mode Exit fullscreen mode

A very simple function. One recommendation you might have stumbled over already is to avoid else as much as possible. In this case, it's a very valid choice to make the code just a hint more readable.

function double(n) {
  if (typeof n === 'number') {
    return n * 2
  }

  return throw Error('double only takes numbers')
}
Enter fullscreen mode Exit fullscreen mode

That code looks quite okay, right? What about the next one?

function isValidPassword(pwd) {
  const includesNeededCharacters = pwd
    .match
    // Lot's of regex logic here
    ()

  return pwd.length > 8 && includesNeededCharacters
}
Enter fullscreen mode Exit fullscreen mode

By glancing at the code, nothing is wrong with it, and it works perfectly and is doing what it's supposed to do. There's only one thing we should fix. And the second code example has this in common with the first one.

By looking at the last line of the second function we actualy qualify the dataset. We don't only check if the password has all the special characters we require, but we also check the length of the string.

What this example and the first one have in common is the fact that we return quite late in the code for something we know it will fail or should prevent further stuff from happening.

Let's do a short refactor and let's break it down in detail.

function double(n) {
  if (typeof n !== 'number') return throw Error('double only takes numbers')

  return n * 2
}

function isValidPassword(pwd) {
  if (pwd.length <= 8) return false

  return pwd
    .match
    // Lot's of regex logic here
    ()
}
Enter fullscreen mode Exit fullscreen mode

What we did here was to break out of these functions very early, as we know that the minimum length is required or the value passed in needs to be of a certain type. We don't need to verify anything else along the way.

Reject early with guard clauses

The first line that returns false, is called a guard clause. It basically guards the rest of the function and checks if some data fulfils the minimum requirement to be allowed to move on the body in the function.

Of course, these are two simplified examples, but realistically you will stumble over functions which will benefit from this pattern very often. Functions that have if/else branches, are often good contendors to be refactored to leverage a guard clause and simplifying code-paths is always a win.

Refactor a complex guard to it's own function

Sometimes, your guard clause might be quite complex. Let's look at the following example.

function postComment(data) {

if!(
  tokenIsValid(data.token)
  && data.userName === current_user.name
  && data.userId === current_user.id
) return response.error('Please sign in again')

// post comment logic
}
Enter fullscreen mode Exit fullscreen mode

The guard clause of this function looks quite complex, and might be hard to validate when briefly glancing at it. Aim for simple guard clauses to fully leverage it's potential and keep your self flexible. We could refactor the function to encapsulate the guard into it's own function.

function requestComesFromValidSession(data) {
  return tokenIsValid(data.token)
  && data.userName === current_user.name
  && data.userId === current_user.id
}

function postComment(data) {
if!(requestComesFromValidSession(data))
  return response.error('Please sign in again')

// post comment logic
}
Enter fullscreen mode Exit fullscreen mode

I hope by now you get the gist of it, and maybe you were even using the concept a few times already without knowing it's name. If not, I can very much recommend to adopt this pattern as it makes your code easier to read and potentially even faster.

Discussion (2)

pic
Editor guide
Collapse
darkwiiplayer profile image
DarkWiiPlayer

A very simple function. One recommendation you might have stumbled over already is to avoid else as much as possible. In this case, it's a very valid choice to make the code just a hint more readable.

Couldn't disagree more. Keep your code 2D: use indentation (along with newlines) to communicate parallelism in code paths. The second example is, in my view, greatly inferior in terms of readability than the first one.

Reject early with guard clauses

With that, the main point of the article, I 100% agree though. Structuring a subroutine as

  1. Reject invalid input data
  2. Process input data
  3. Return results

makes it easier to follow the logic. We're used to having information about the input parameters at the beginning of the function (usually in the function declaration), and putting additional constraints right after that makes sense.

Collapse
shavrin profile image
Kacper Olek

Cool article! Learned something new.
One thing: there's a typo in the last code snippet, ! is outside the parentheses.