Indenting code, while usually not useful to a compiler, greatly helps us humans read code more easily. However, adding more indentation than what is necessary––due to extraneous if
statements, for example––might make a piece of code harder to read. Here is a brief overview of one technique you can use to avoid over-indenting code.
Take a look at the following simplistic example of a function getUserImages
, which fetches a user's images from an API and then maps the data in some way:
const getUserImages = async (userId) => {
if (userId) {
const getImagesResponse = await apiClient.getImages(userId);
if (getImagesResponse.ok) {
const images = getImagesResponse.value;
if (images) {
const mappedImages = mapImages(images);
return mappedImages;
}
}
}
};
Yikes, that's a lot of indentation 😬. You can imagine how complex code written like this might be hard to follow: With more indents, it becomes harder to track the block of code that a certain line belongs to.
The good news is that we can avoid a lot of this indentation! In this case, it's pretty simple because there are no corresponding else
blocks; we can simply check for the inverse of the if
statements and return early if the conditions aren't met.
Here's a refactored version of getUserImages
using this technique:
const getUserImages = async (userId) => {
if (!userId) {
console.log("No userId provided");
return;
}
const getImagesResponse = await apiClient.getImages(userId);
if (!getImagesResponse.ok) {
console.error("Error getting images!");
return;
}
const images = getImagesResponse.value;
if (!images) {
console.log("User has no images");
return;
}
const mappedImages = mapImages(images);
return mappedImages;
};
We've "flattened" the code out a bit and made it easier to read. Note that early guards like !images
aren't very useful if there isn't a lot of code underneath it, but, again, this is a simplistic example.
This technique can also apply to other places where we might have multiple nested blocks of code, like in a for
loop:
const processList = (list) => {
for (let i = 0; i < list.length; i++) {
if (i % 2 === 0) {
if (list[i]) {
// ... Do something
// ... Do more things
// ... Do even more things
}
}
}
};
I find that immediately nesting if
statements like this is usually difficult to read. The code in the inner if
statement is indented four times; removing even one level of indentation can help us out:
const processList = (list) => {
for (let i = 0; i < list.length; i++) {
if (i % 2 !== 0) {
continue;
}
if (list[i]) {
// ... Do something
// ... Do more things
// ... Do even more things
}
}
};
This technique can't always be used to refactor hard-to-read indented lines of code. But, when possible, removing unnecessary indentation in your code like this will go a long way in making it more readable and maintainable for yourself and future developers.
Let's connect
If you liked this post, come connect with me on Twitter, LinkedIn, and GitHub! You can also subscribe to my mailing list and get the latest content and news from me.
Top comments (3)
These are quite poor examples TBH. The refactoring of the first one has added error logging, which confuses matters as the new code actually does something different to the original.
In both refactoring cases (particularly in the 2nd, but also in the 1st if the logging were not added) it probably would have been easier (and possibly more efficient) to simply combine the
if
statementsWhat you're describing is a valid point, it's a form of the source code optimization, but you're naming it wrong. The indentation is quite insignificant property of a source code, it's there just for better readability by humans and it's more (e.g. in case of Python) or less just a consequence of the cyclomatic complexity. And the cyclomatic complexity is, in fact, what you're trying to optimize, more specifically it's called optimization by reduction of the cyclomatic complexity.
When you reduce the cyclomatic complexity, the indentation is also reduced as the result or the consequence of such optimization. And such reduction has an impact on several properties of the source code (e.g. testing, clarity, code organization, performance, etc.).
On the other side, when you reduce the indentation, it doesn't have any impact on the cyclomatic complexity, you're just changing the readability of the source code.
Thomas McCabe, who came with the term cyclomatic complexity in 1976, proposed that the cyclomatic complexity of a module should be kept under 10.
I hope it's understandable as I have described it.
As much as I love my extensions that measure the cc of my functions, there's cases where it goes off the rails. A function with only a switch statement, where every case is a single-line return statement, for example. The "parallelism" of the code makes it easy to follow for humans even if by math it's complex.
I also like things like SonarQube / ReSharper / etc for measuring cc and for the "reduce nesting" auto-refactor which fixes the too-much-indentation issue.