8 Simple Tips that Make Your Javascript Code More Readable & Clean Code

Dafa Pramudya
7 min readNov 29, 2023

بِسْــــــــــــــــــمِ اللهِ الرَّحْمَنِ الرَّحِيْمِ

السَّلاَمُ عَلَيْكُمْ وَرَحْمَةُ اللهِ وَبَرَكَاتُهُ

Hi guys, it’s me again 😁

In this article, I will share with you some simple tips that make your JavaScript code more readable and clean code.

Before we jump to the topic, let’s talk about the Clean Code, what is it?

Clean code is a concept in software development that emphasizes writing code that is easy to understand, maintain, and test. It involves principles such as readability, simplicity, avoiding redundancy, meaningful naming, single responsibility for functions or methods, effective error handling, clear communication through code, and testability. The goal of clean code is to make the source code understandable for both current and future developers, fostering collaboration and facilitating the ongoing maintenance and improvement of the software. The principles of clean code are outlined in books like “Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martin.

Imagine if you have a lot of complex code, and you don’t apply the principles of clean code, then you will find it very difficult to maintain the code.

Here are 8 tips that make your javascript code more readable and clean code:

1. Always use meaningful names for variables

Variable names are the most basic and simplest things in programming, but they have a significant impact when applied carelessly, therefore, we must always use meaningful variable names so that the code we create is easy to understand.

//DONT
const a = "Dafa Pramudya"
const b = "Male"

//DO
const fullName = "Dafa Pramudya"
const gender = "Male"

Note that you must use the camelCase format when naming the variable. (i.e: word1 / word1Word2).

When naming boolean variables, it’s generally a good practice to use names that convey the meaning of the condition being checked. Here are some examples:

const isActive = true;
const canEdit = false;
const hasPermission = true;

Also if you have a variable that is used to store the result of a function use a noun or add the word ‘result’ in front of the word:

const cars = getCars()
const carById = getCar(id)
const updatedCar = updateCar(id)
const resultDeletedCar = deleteCar(id)

This also applies to function naming 🆒.

2. Avoid creating functions with many parameters

Creating functions that have many parameters is not a wise practice, because sometimes it will make us confused, especially if other people read our code 😁, such as what parameters need to be filled in, whether mandatory or not, especially if there are more parameters added in the future. The best practice is, we need to set the maximum parameters in each function. At least maximum 3–4 parameters.

In some cases, when we need to pass many parameters to a function. For e.g. register function, we just need to create an object that includes all the data that needs to be passed to the function, so the function will have fewer parameters:

//DONT
function registerUser(fullName, email, phoneNumber, gender, address, hobbies) {
//Logic register user
}


//DO
function registerUser(data) {
const {
fullName, email, phoneNumber,
gender, address, hobbies
} = data
//Logic register user
}

Then, in the register user function, we can use the destructive operator to get the data.

3. Avoid creating condition that contains complicated checks

Condition that contains too complicated checks will make it very difficult to read, so it is better to create a function to handle the complex condition.

Create a function that returns the result of complex checking, so that later we only need to call the function in the condition of:

//DONT
if(role === 'admin' && projectMember.length && projectStatus === 'confirmed') {
//Logic function
}


//DO
const isAdminCanApprove = checkAdminCanApprove(data);
if(isAdminCanApprove) {
//Logic
}
//Or (Alternative)
if(isAdminCanApprove(data)) {
//Logic
}

function checkAdminCanApprove(data) {
try {
const {
role,
projectMember,
projectStatus
} = data;
if(role === 'admin' && projectMember.length && projectStatus === 'confirmed') {
return true;
}
return false;
}catch(err) {
//Error handling
}
}

It seems that the “DO” code is even more, but actually, this “DO” code will make it easier for us to read the condition, because when we see the condition, we can immediately know the purpose of the condition, which is to check whether the admin can approve. This method also strongly supports the DRY (Dont Repeat Yourself) method, which is when we encounter the same checking condition, we only need to call the function that we have created before.

4. A function only performs one task

Imagine if we have a function that has many tasks, maintaining that function will definitely be very difficult. So we need to create a function that only performs one task. This is often referred to as the Single Responsibility Principle (SRP), one of the SOLID principles of object-oriented design.

When a function is focused on a single task, the function will be more readable, more maintainable, and easier to test.

// Non-SRP (Single Responsibility Principle)
function processUserDataAndGenerateReport(user) {
// Process user data
// ...
// Generate report
// ...
}

// SRP (Single Responsibility Principle)
function processUserData(user) {
// Process user data
// ...
}

function generateReport(user) {
// Generate report
// ...
}

Applying SRP can lead to cleaner, more modular code that is easier to understand and maintain over time. It also promotes the idea of composing smaller functions to achieve larger tasks, contributing to a more functional and modular programming style.

5. Logging is a must

Programmers are not sorcerers, log is very important and our primary weapon. With a log, we can trace any incoming process. For example, if there is an error in production, logs can be evidence to explain to our boss or users, so we can fix it easily.

Always put logs on data processing, so we can know the process our code is doing. For success cases, it doesn’t need to be mandatory, but for error cases, we should really put logs, and we even need to put error notifications so that we are immediately aware of the errors that occur and fix them.

function processMultiplyDataBy2(data) {
console.log('Processing data started:', new Date());

try {
const processedData = data.map(item => item * 2);

console.log('Data processing successful:', new Date());

return processedData;
} catch (error) {
console.error('Error during data processing:', error, new Date());

throw error;
}
}



const dataNumbers = [1, 2, 3, 4, 5];

try {
const result = processMultiplyDataBy2(dataNumbers);
console.log('Processed data:', result);
} catch (error) {
console.error('Error handling:', error);
}

Use log level (e.g., info, warning, error) because it can help to categorize the severity of messages. This allows developers to filter and focus on relevant information.

6. Always use ‘try-catch’ when creating functions

When we need to trace or handle an error we can use a try-catch block. Use try-catch when you want to handle potential errors that might occur during the execution of your function. This is particularly important for critical operations or when dealing with external resources.

function divide(a, b) {
try {
if (b === 0) {
throw new Error('Division by zero is not allowed.');
}
return a / b;
} catch (error) {
console.error('Error:', error.message);
// Optionally, handle or log the error.
// You may also rethrow the error if necessary.
}
}

When you have an asynchronous function, a try-catch block is a must to be implemented to the function, because if you did not implement a try-catch in your async function, unhandled errors could result in your application crashing or behaving unexpectedly.

async function fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error.message);
// Handle or log the error as needed.
}
}

7. Create a short documentation comment related to the declared function

Code documentation is very important, especially when the code we create will be used by others. The hope is that when others look at our code, they immediately understand the purpose of our code.

Method / function is one of the complex code statements, so if we put a function documentation comment above the function is declared, it will make it easier for us to know the purpose of the function.

As we know, javascript is not a strictly typed code, so if we define the comment of the return function and its parameters, it will be very easy to read.

/**
* Get haveCertificate from user
* @param {object} user - user data (default value = {}).
* @returns {Boolean}
*/
const haveCertificate = async (user = {}) => {
try {
const {certificate = []} = user;
if(certificate.length) {
return true;
}
return false;
}catch(err) {
//Error handling
}
};

8. Create code that can handle unpredictable null objects

An unpredictable null object occurs when we have a nested data object that sometimes the key of the nested object is null or changed, so if we try to access the key of the nested object, we will get the undefined result. This case can make the code crash. If you’ve ever encountered this case, you might have gotten an error like ‘Cannot read property ‘data’ of undefined’.

The solution to this problem is that we can check the object first before we access the deeper nested object. We can use OR (||) conditional, optional chaining, and nullish coalescing operator (??):

const user = {
name: 'Dafa',
gender: 'Male',
age: 23
};

//Using or conditional
const city = ((user.address || {}).city || 'city not yet defined');
//Using optional chaining
const postalCode = user?.address?.postalCode || 'postal code not yet defined';
//Using nullish coalescing operator (??)
const hobbies = user.hobbies ?? 'users have no hobbies';


console.info(`${user.name} \n\n${city}\n${postalCode}\n${hobbies}\n`)

We can handle unpredictable null objects on parameter functions, just add the default value on the parameter:

//The default value of user params is object {}
const haveCertificate = async (user = {}) => {
try {
//certificate variable have the default value []
const {certificate = []} = user;
if(certificate.length) {
return true;
}
return false;
}catch(err) {
//Error handling
}
};

That’s all that can I share with you in this article, if you have more tips about clean code in JavaScript don’t forget to comment on this post. Let’s learn together 😎

See you in the next article.

وَ السَّلاَمُ عَلَيْكُمْ وَرَحْمَةُ اللهِ وَبَرَكَاتُهُ

--

--