4 tips for cleaner, more readable javascript

4 tips for cleaner, more readable javascript

April 23, 2021

I know, your project is under a super tight deadline and your team is way over capacity this sprint and the release is overdue. Got it, I've been there too plenty of times and I totally understand your plight. This is typically why our javascript misses some attention. Tight deadlines have been the culprit of messy code for as long as projects have had deadlines. Let's be honest though, your code needs to be readable by your teammates and your code is complex. Even the easy stuff is complex in the context of an entire project. As a developer its not only your job to code your story or get your ticket through QA, but it's your job to write your Javascript in a way that can be easily updated by someone else several months or years later. Don't worry, I got you covered with some easy tips that should help you write cleaner, more team-oriented Javascript. ?

1 – Avoid for loops

They’re a little hard to read and mentally parse (and, not to brag, but I haven’t written one in years). Let’s look at this example. Let’s say you have an array of objects and you just want an array of strings – just the names of the dogs.

let dogs = [
    {
        name: "Bo", 
        breed: "Siberian Huskey"
    },
    {
        name: "Linda",
        breed: "Bernese Mountain DOg"
    }, {
        name: "Fisher",
        breed: "Pembroke Well Corgi"
    },
    {
         name: "Silver",
         breed: "Irish Setter"
    }
];

Here’s what we could do:

let dogNames = [];

for (let i = 0; i < dogs.length; i++) {
    dogNames.push(dogs[i].name);
}

We start by creating an empty array dogNames. Then we have our for loop. The first line of the for loop is basically boilerplate for (let i = 0; i < dogs.length; i++). We're all used to it so we know whats going on without really having to "read" the code, but in more complex examples it can be difficult to decipher. Next, we are pushing the name of the dog into the empty array we created dogNames. This is just a lot of code that is basically saying, loop over the existing array and create a new array of just the names of the dogs. Using the Javascript array method .map() we can accomplish the same thing more succinctly.

let dogNames = dogs.map(dog => dog.name); 

Now, just because it's shorter doesn't mean I think it's easier to read. I usually argue that over-communicating (more words) is far better than under-communicating. But, ideally we don't want to provide a bunch of unneeded information if we don't have to, because that can make our code more difficult to understand. Usually when starting on a new ticket or story there is a lot of code that we have to mentally review and understand. We want to minimize the mental overhead of the developer so they can start contributing quicker. The .map() function is much easier to read in my opinion because it says the same exact thing as the for loop in a much more condensed format. It gives you exactly enough information to know exactly what's happening. So what is happening? We created a new variable called dogNames and then assigned it an array of strings (dog names) by mapping over the dogs array and returning an equal length array of just the dog.name. The arrow function syntax allows us to implicitly return the dog.name without using the return keyword. It's the same as writing this:

let dogNames = dogs.map((dog) => {
    return dog.name;
}); 

The above is a little longer, almost as long as the for loop way, but still more concise in its nature, because we don't need the boilerplate loop syntax or the need to create an empty array and push new values to it with each iteration of the for loop. I'm fine with either the more condensed implicit return or the more verbose explicit return example, as the first version is more compact, but the second version doesn't provide any additional mental overhead in my opinion just because its longs. My position that using the .map() function assumes that the developer has an understanding of Javascript's arrow functions and array methods, which I highly recommend every developer become familiar with Array methods to level up their skills and more easily work with arrays.

Other array methods like .filter(), .find(), .findIndex(), and .some(), to name a few, are a readable step up from for loops.

2 - Use descriptive variables instead of expressions in conditional logic

Let's say you need conditional logic. What is easier to read:

// Choice 1
if (recentEntry[1] && moment().isBefore(recentEntry[1]) { ... } 

// Choice 2
let entryNotExpired = recentEntry[1] && moment().isBefore(recentEntry[1]);
if (entryNotExpired) { ... }

Of course, choice 2 is way easier to wrap your head around. What is recentEntry[1] && moment().isBefore(recentEntry[1]) actually doing? Not sure to be honest, except that it's doing some checks that result in true if the 'entry' isn't expired. That's good enough to not have to necessarily dig through the code and mentally parse this to understand what's going. That's what we do as developers, and I hinted at this earlier. We try and execute this code using our brain as the run time. It's a slow and inefficient way to execute code. Our brain is much better at understanding entryNotExpired and allow us to move through many lines of code to find what we need. Any time you have an expression in an if statement, especially if there are some logical && and || operators, be sure to cash in your expressions for descriptive variable names.

3 - Document your variables and functions with Types

So basically use Typescript. If you work on an Angular or React project you probably use Typescript already, but check out the difference here:

// example 1
const callDuration = this.getCallDuration();

// example 2
type CallDuration = {
  hours: number;
  minutes: number; 
  seconds: number;
}
const callDuration: CallDuration = this.getCallDuration();

It's obvious that example 2 provides so much more information. In example 1, we have no idea what the variable name, callDuration is. Is it a string, a number, boolean? In example 2, we see that it's an object with 3 properties: hours, minutes, and seconds, all of which are number types.

No matter what, add types for variables and function returns, which gives you an added layer of documentation. It's easy to add any to satisfy the linter, or omit the type all together. But by adding the type you're adding meta data to your functions and variables that tell your future self and teammates what you intended.

4 - Comment your functions heavily

If you don't write comments, other devs will still be able to figure it out. It will just be more painstaking. It could require more mental code execution. More checking other files to see what the functions you're calling are meant to do. They might have to do more Google searching. They might have to do more console.log()'s. They might even have to call you to find out what it's doing.

There are a few benefits to commenting your code. First, you can put down that you are a good communicator in your annual review. Okay, only half joking there. Commenting your code is a great way to communicate your intentions of the code with your colleagues. A good code comment explains - "Here's my intention with this. Here were my assumptions. If those assumptions change, don't hesitate to switch things up." When you are doing a refactor of something, you can be more confident that you are not breaking things because the comments help explain what's going on.

Second, prior to submitting your pull request, when you comment your code you end up having to explain yourself. When trying to make sense of my own work as if I was explaining it to someone else, I often see opportunities to make the code more readable, more performant, or in someway better. It's a self-review that takes place that usually does more than just explain what a function is doing but produces a derivative of better code along with it.

Third, the obvious one, everyone understands what's going on in your code a little bit better.

You might be thinking though, that you only have a few super simple functions, like toggleButton(). It's so simple that it basically self documented itself... Except in the beginning though where we have to accommodate the edge case scenario or the error scenario. Or better yet, the edge case error scenario for new users with no data yet. So you have some code in there that is not super self explanatory to account for these scenarios. It took a long time to uncover what was needed to account for these scenarios. That's especially where you want to add your comments.


// Validates the dog name field when creating a new dog or editing an existing dog
function validateDogName(dogName: string): boolean {
  // make sure the dog's name isn't blank before proceeding
  let blankName: boolean = dogNameValue.trim() === "";
  if (blankName) {
    return false; // the name field is empty
  }
// the dog's name can only be 20 characters maximum
  let nameTooLong: boolean = dogNameValue.length > 20;
  if (nameTooLong) {
    return false; // the name is too long
  }
  // all validations passed
  return true;
}

This example has comments and also benefits from having typescript types describe what the variables and return values are, and it is using descriptive variable names inside the if statement so our brains don't have to serve as a runtime to mentally execute our code.

Conclusion

An undercurrent in these 4 tips for keeping your javascript clean and readable, is that someone else will likely be reading, understanding, and updating your javascript. In order for them to do that, they'll have to mentally execute your code with their brain of all things. Our brain's are inefficient, buggy run-time environments. Comments, types, descriptive variable names, and intuitive array methods (like map, find, filter, and sort) add hints and additional information that documents our code. This makes it easier when it comes time to update and build upon code. And that's what we all want is the ability to update and add features quickly and confidently that new bugs won't be introduced. When we understand the intentions and reasoning behind code we can update and maintain code with more assurance because we know more about it. We understand it better.

Broom SVG credit Eucalyp via the Noun Project

Mastering CSS: Book by Rich Finelli
Mastering CSS, the Book! Flexbox, Layout, Animations, Responsive, Retina, and more!
Mastering CSS, 2nd Edition, video course by Rich Finelli
Mastering CSS, Second Edition: 47 videos on how to make websites like a boss! Flexbox, Animations, Responsive, Retina, and more!
Back to top