Writing Cleaner JavaScript with Guard Clauses

When I first started programming with JavaScript, my conditional logic statements were often comprised of massive amounts of nested if…else if…else statements. Initially, when writing these statements, they made sense because they walked through the logic step-by-step and the function executed properly based on the logic. Hoverer, weeks or months later when I needed to update or change the logic in my massive conditional blocks, I would have to parse through and understand the logic all over again. Even with the simplest functions, parsing through multiple levels of nested conditional logic was a difficult and time-consuming task. On top of that, I would have to spend a lot of time debugging and restructuring my code in order to keep everything working with the new changes I added.

At first, I thought that the frustration and time involved in working and reworking the conditional logic was unavoidable and just ‘part of the territory’, even if it was frustrating and seemed counter-productive. I was writing the logic exactly the way I worked through it in order to create the function.

Over time and with lots of practice and time spent of Google, I learned that there is an easier, cleaner, and more sensible way to write my conditional logic functions that achieve the same results but that look cleaner and are much easier to follow. I learned about the ‘magic’ that is guard clauses.

What is a Guard Clause?

A guard clause is a piece of conditional logic that is placed at the beginning of a function that will return out of the function early on if certain conditions are not met. Guard clauses are simple to implement in any function that involves conditional logic, and they make functions shorter and cleaner.

Let’s take a look at a very simple example of how guard clauses can help shorten and clean up conditional logical functions. Below is a function that determines if a player has already attacked a specific location:

function checkForPlayerStrike(strike) {
if (playerStrikes.includes(strike)) {
error = true
error.message = 'You already hit this location.'
}
}

This function is relatively straightforward — if the playerStrikes array contains the current strike, then the player has already hit that location and an error message is displayed.

How would we rewrite this function using a guard clause to clean it up a little bit? Here’s an example:

function checkForPlayerStrike(strike) {
if (!playerStrikes.includes(strike)) return
error = true
error.message = 'You already hit this location.'
}

The above function does the same thing that the first one does. The logic is the same, but the function is only run if the playerStrike array contains the specified strike area. You will notice that the guard clause is the opposite of the conditional logic statement in the first function, and it will simply return out of the function without running it if the condition is not met. The actual logic is also no longer nested inside of a conditional statement since it will only be executed if the guard clause is false.

Adding a guard clause to a function as simple as this may not seem to make a lot of sense, as the difference is not significant. To further demonstrate the usefulness of guard clauses, let’s take a look at a more complex function.

The below function might be an example of a function that might be used to determine the deductible that a patient pays out of pocket for a certain medical procedure:

function patientDeductuble(procedure) {
if(procedure.covered) {
if(procedure.inpatient) {
return 2000
} else if (procedure.outpatient) {
return 1000
} else {
return 1500
}
} else {
return 'Procedure is not covered by insurance!'
}
}

Looking at this function, it might be a little bit difficult to decipher exactly what is going on at first glance. You have to dig into the function line by line to determine exactly what the logic is and what the function returns, based on the condition. We could clean this function up by using guard clauses and the result might look like this:

function patientDeductible(procedure) {
if (!procedure.covered) return 'Procedure is not covered by insurance!'
if (procedure.inpatient) return 2000
if (procedure.outpatient) return 1000
return 1500 // Procedure is Elective.
}

The above function is a lot cleaner and easier to follow with a quick glance. It is also much shorter and much cleaner to look at. The logic of the function is the same as in the first function, and it produces the same result, but instead of nesting the logic within itself, each piece of logic is self-contained in the form of a guard clause.

You will probably also notice that the logic is the inverse of the logic in the first function. This is an aspect of writing guard clauses that takes some getting used to. It takes a little bit more effort to write a function cleanly using guard clauses, but the little extra effort is worth it, especially when you or another developer return to your function in the future to review and update the logic.

Write your Logic First, Then Refactor

When you go to write your functions, it is best to write the function the long way first, using the nested conditional logic method, make sure it works, and the go back and refactor it to a cleaner, more elegant function using guard clauses.

The reason for this is because when we write out a function, it is easier to write the function to follow our thought process and how we would break the function down step by step. No programmer that I have ever met can start off by writing clean functions. The first time any programmer writes their functions, they will most likely look a lot like the first function I demonstrated. Once the function is working and does what it is intended to do, a programmer will go back and refactor it to look more like the latter example above.