Writing Cleaner Code

Everyone wants to write clean code. There are whole books about it!
But you don't need to read a book to write cleaner code right now. There's one "trick" that every coder can learn to make their code less confusing.
Avoid Unnecessary Nesting
Nesting in code is something we do all the time, and although there's nothing inherently wrong with nesting, it can sometimes make code harder to read. One approach to help avoid this is to use the "Return Early" design pattern. It allows us to use the if statement as a guard clause to check for errors and return before executing any further code. It helps avoid the use of if/else and unnecessary nesting.
Let's assume we have a function where the if statement wraps the entire function:
function deleteItem(item) {
if (item != null) {
console.log("Deleting item");
item.delete();
}
}function deleteItem(item) {
if (item != null) {
console.log("Deleting item");
item.delete();
}
}Although there is essentially nothing wrong with this, we can make it cleaner by using a guard clause. So instead of checking to see if the item is not null, we negate this and return nothing if the item is null:
function deleteItem(item) {
if (item == null) return;
console.log("Deleting item");
item.delete();
}function deleteItem(item) {
if (item == null) return;
console.log("Deleting item");
item.delete();
}Both implementations behave identically, but as you can see the second implementation is obviously cleaner. Now let's look at an example of a nested if statement:
function saveItem(item) {
if (item != null) {
console.log("Validating");
if (item.isValid()) {
console.log("Saving item");
item.save();
}
}function saveItem(item) {
if (item != null) {
console.log("Validating");
if (item.isValid()) {
console.log("Saving item");
item.save();
}
}Again, there is nothing really wrong with this implementation, but we can change the way we use the if statements to completely remove the nesting from this function:
function saveItem(item) {
if (item == null) return;
console.log("Validating");
if (!item.isValid) return;
console.log("Saving item");
item.save();
}function saveItem(item) {
if (item == null) return;
console.log("Validating");
if (!item.isValid) return;
console.log("Saving item");
item.save();
}As you can see, the "return early" approach can help make your code more linear, cleaner, and more readable. It's a simple technique that is easy to implement.
Use Object Destructuring For Function Parameters
Let's assume we have a function that takes an object as the parameter and performs some kind of operation on that object to return a new value. Without using object destructuring, we might get something like this:
// not so good
function getFullName(person) {
const firstName = person.firstName;
const lastName = person.lastName;
return `${firstName} ${lastName}`;
}// not so good
function getFullName(person) {
const firstName = person.firstName;
const lastName = person.lastName;
return `${firstName} ${lastName}`;
}This implementation works just fine, but we are creating two temporary references firstName and lastName when we don't really need to.
A better way to implement this is to use object destructuring. We can destruct the person object to get both the first name and last name in one line:
// better
function getFullName(person) {
const { firstName, lastName } = person;
return `${firstname} ${lastName}`;
}// better
function getFullName(person) {
const { firstName, lastName } = person;
return `${firstname} ${lastName}`;
}We are able to take this a step further by destructuring the parameter directly, eliminating another line of code.
// even better
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}// even better
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}A much more elegant way of writing the same function.
Avoid Side Effects By Using Pure Functions
// bad
let items = 5;
function changeNumber(number) {
items = number + 3;
return items;
}
changeNumber(5);// bad
let items = 5;
function changeNumber(number) {
items = number + 3;
return items;
}
changeNumber(5);Here whenever we call this function, it will modify the items variable which is best to avoid doing, since we are technically modifying our code which can lead to unpredictable or undesirable behaviour. The other thing we want to avoid is reliance on external variables within a function. If the items variable suddenly didn't exist or changed it's data type, it will obviously cause problems we want to avoid.
So instead, using a pure function, we can rewrite the function to the following:
// good
function addThree(number) {
return number + 3;
}// good
function addThree(number) {
return number + 3;
}Here we have removed the dependency of the external variable and made the function return a new value. It's functionality is now fully self contained therefore it's behaviour is now fully predictable. A much better approach.
Keep Functions Simple
When writing functions, it can be tempting to put multiple things in one place:
function signUpAndValidate() {
// Do a heap of stuff here
}function signUpAndValidate() {
// Do a heap of stuff here
}but this can quickly lead to spaghetti code and unwanted bugs. It is best to keep functions responsible for one thing only. This is a better approach:
function signUp() {
}
function validate() {
}function signUp() {
}
function validate() {
}Always Use Meaningful Variable Names
When coding it can be easy to be lazy and use single letter variable names at times, but this just causes you headaches down the track. here are a few tips to help you out.
"There are only two hard things in Computer Science: cache invalidation and naming things" – Phil Karlton
- Functions perform actions, so use verbs when naming them.
// bad
function passwordValidation() {
}
// good
function validatePassword() {
}// bad
function passwordValidation() {
}
// good
function validatePassword() {
}- Use is when using boolean type:
const isValidPassword = validatePassword("abcd");const isValidPassword = validatePassword("abcd");- Use plurals for arrays: Be careful about words that mean something specific. Do not refer to a grouping of accounts as
accountListunless its type is actually aList. The word has a specific meaning and it may lead to false conclusions. Even if the type is a list,accountsis a simpler and better name.
const animal = ["cat", "dog", "bird"];
const animals = ["cat", "dog", "bird"];const animal = ["cat", "dog", "bird"];
const animals = ["cat", "dog", "bird"];- When using callback functions, always use a meaningful name when iterating:
// don't do this
animals.forEach((a) => {
console.log(a);
});
// do this
animals.forEach((animal) => {
console.log(animal);
});// don't do this
animals.forEach((a) => {
console.log(a);
});
// do this
animals.forEach((animal) => {
console.log(animal);
});Avoid Magic Numbers
Avoid using magic numbers in your code. Opt for searchable, named constants. Do not use single-letter names for constants since they can appear in many places and therefore are not easily searchable.
Bad:
if (student.classes.length < 7) {
// Do something
}if (student.classes.length < 7) {
// Do something
}Good:
if (student.classes.length < MAX_CLASSES_PER_STUDENT) {
// Do something
}if (student.classes.length < MAX_CLASSES_PER_STUDENT) {
// Do something
}This is much better because MAX_CLASSES_PER_STUDENT can be used in many places in code. If we need to change it to 6 in the future, we can just change the constant.
Fewer Arguments
Functions should have 6 or fewer arguments, the fewer the better. Avoid three or more arguments where possible.
Arguments make it harder to read and understand the function. They are even harder from a testing point of view, since they create the need to write test cases for every combination of arguments.
Do Not Use Flag Arguments
A flag argument is a boolean argument that is passed to a function. Two different actions are taken depending on the value of this argument.
For example, say there is a function that is responsible for booking tickets to a concert and there are 2 types of users: Premium and Regular. You can have code like this:
public Booking book (Customer aCustomer, boolean isPremium) {
if(isPremium)
// logic for premium book
else
// logic for regular booking
} public Booking book (Customer aCustomer, boolean isPremium) {
if(isPremium)
// logic for premium book
else
// logic for regular booking
}Flag arguments naturally contradict the principle of single responsibility. When you see them, you should consider dividing the function into two.