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')
}
}
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')
}
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
}
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
()
}
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
}
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
}
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.
Top comments (2)
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.
With that, the main point of the article, I 100% agree though. Structuring a subroutine as
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.
Cool article! Learned something new.
One thing: there's a typo in the last code snippet, ! is outside the parentheses.