How to Write Clean Code in Javascript?
Today I want to show you 5 code improvements which will help you to write clean Javascript code.
Use explanatory variables
Here is our first example.
// bad
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
address.match(cityZipCodeRegex)[1],
address.match(cityZipCodeRegex)[2]
);
So we have an address and a regular expression which we store in variable. This is fine. But after this we have saveCityZipCode
where we provide 2 calls of match function with our regular expression.
Why is this code bad? First of all we call exactly the same match twice. Which means it's not really good for performance. Secondly we didn't store this property anywhere and we don't have a clue what we get inside first and second element of the array. So we have zero understanding what we provide inside our saveCityZipCode
function.
How we can fix this? We can move match call to the additional property and name our elements by destructuring.
// good
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex)
saveCityZipCode(
city,
zipCode
);
We don't need first element so we just skip it. With destructuring we can name our elements so here we can clearly see what we get from our regular expression. Now we don't have any questions regarding this code, sure we have an unclear regular expression but we know what we get back and what we provide in saveCityZipCode
function.
Avoid mental mapping
The next this is mental mapping and here is an example.
// bad
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(l => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(l);
});
So here we have a locations array and a loop through each element and we do some stuff inside. And sure we understand what we do in this code and thatl
property means location. But normally your code will be more complex and at the moment when you read dispatch(l)
we already forgot what is l
. Which actually means that we must have a mental mapping in our head between l
and location. It simply takes the resources of our brain and we can avoid this. We simply need to name variable location
and we don't need to remember what is l
.
// good
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(location => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});
In this case we simply read what we do. This is why I highly recommend you to always use long-long-long names because it must be crystal clear what is it there inside that specific variable.
Default values
The third case which is really important is to use modern Javascript features. And I'm talking here about default values inside functions.
// bad
function createMicrobrewery(name) {
const breweryName = name || "Hipster Brew Co.";
// ...
}
As you can see we have a function createMicrobrewery
and we pass a name inside. After this we check if we passed a name and if not then we set default value. This code is completely fine but we are not leveraging here the Javascript functionality that already exists. We have default values feature inside Javascript and I highly recommend you to use it. In this case here we don't need to create additional property because we can set the default value for the argument.
// good
function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}
Now if we don't pass this name inside, we will use this default name here. It makes your code easier to read because you have simply less variables.
Function arguments
The next case that I see really often is that people pass to many arguments as function parameters.
// bad
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);
As you can see here we have 4 arguments inside our createMenu
function. And normally some developer just started with 1 or 2 arguments. Later somebody needed a buttonText and state for cancellable. It is already difficult to support because when I see such all of our function I don't have a clue what all this arguments mean. So I need to just inside function definition every time just to check what argument does. This is why I highly recommend you to write not more that 2 arguments. It is really easier to read and support.
But how we can fix this code if we need to pass more that 2 arguments? We can create a single argument which is an object instead.
// good
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});
Here we put all our arguments inside an object and destructured them in our function. This is why the logic of the function is not changed at all and now we can see from outside what we pass and what it means.
Default config
One more important case is how we can merge our default configs.
// bad
function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);
Here we have a function create menu where inside we pass some config. The main point here that this config
is partial. Which means we have some default values and we don't need to pass all properties inside every single time. Which is completely normal. This is why we check "Did we pass a title?" if no then we take default value everywhere.
And here are 2 problems. First of all this code is verbose and difficult to read and secondly we can make a default config and it will be clear what we do here.
// good
function createMenu(config) {
const menuConfig = {
title: "Order",
buttonText: "Send",
cancellable: true
};
return Object.assign(menuConfig, config)
}
createMenu(menuConfig);
So we have a default menuConfig
and we just use Object.assign
to update default config with our new values that we passed. Most importantly Object.assign
doesn't just replace the whole object but merges it and returns new object. This code is much easier to support because we just have a single default state, we can always tune it and we really see what is going on inside this function.
Bonus: Project vocabulary
Here I prepared 2 important bonus cases for you. First of all you need to prepare a nice vocabulary for your project. What does it ever mean?
// bad
getUserInfo();
getClientData();
getCustomerRecord();
// good
getUser();
As you can see here I have bad example and good example. The main idea is that we need to have a set of entities and names inside our project. For example we have an entity user
which means in all our functions we need to work with user. This is why the good function is getUser
, fetchUser
, updateUser
. In all this cases we know that we work with user.
On the top you can see bad cases. getUserInfo
, getClientData
, getCustomerRecord
and it might be that all this functions are about our entity user. But it is really bad for our mindmap inside our brain because we don't have a strict vocabulary for our project. This is why it is super difficult to debug such code if we have different naming for the same thing.
Bonus: Magic numbers
My second bonus point is regarding magic numbers.
// bad
setTimeout(blastOff, 86400000);
// good
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;
setTimeout(blastOff, MILLISECONDS_PER_DAY);
As you can see here the bad example just uses some random number and nobody knows what it means. It is bad because we don't know what this number means or how to change it.
Our good example stores the same number ins a variable and we know what it means but we also not just store a plain value but a calculation which everybody can understand. We multiply seconds on minutes on 24 hours. It is extremely important because it is easier to tune it like this. For example we can change 24
in 12
and the logic of our calculations will still be clear.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!