Writing clean code is the key to creating applications that are maintainable and scalable. In this lesson, we are going to cover some of the rules you should follow in order to write clean code using JavaScript.
Write readable code
First of all, you should make sure the variables, functions and classes are properly names. The name should describe the purpose of the code, and also readable. For instance, the following examples are not appropriate:
1// This example is too short
2function a(b, c) {
3 return b + c;
4}
5
6// This example is too complicated
7function addNumberOneAndNumberTwo(numberOne, numberTwo) {
8 return numberOne + numberTwo;
9}
If the name is too short, it does not describe exactly what this function does. If the name is too complicated, they lose readability. Instead, you must ensure the name is descriptive but also concise like this:
1function add(num1, num2) {
2 return num1 + num2;
3}
This is a good name because add
accurately describes the purpose of this function, but not too complicated like the previous example.
Write descriptive comment
You must also make sure that your code is properly commented. Whenever you create a function, a class, or whenever you create a new feature, on top of the descriptive names, there should also be a comment explaining exactly what the code does.
Similarly, the comment should also be descriptive and concise. The following example is too long and difficult to read.
1// This function calculates the area of a rectangle by multiplying the rectangle's width and height.
2function calculateArea(width, height) {
3 return width * height;
4}
And this example is too short and doesn't add any information besides the function name.
1// Calc area
2function calculateArea(width, height) {
3 return width * height;
4}
A proper comment should describe the purpose of the code, and readable at the same time.
1// Calculate the area of rectangle given width and height.
2function calculateArea(width, height) {
3 return width * height;
4}
Handle errors gracefully
No matter how good of a programmer you are, errors are inevitable, especially when working with user inputs, where all kinds of unexpected situations could occur. Without proper error handling, a small mistake could crush your entire application.
It is very important to implement error handling in your app, so that it could gracefully handle unexpected situations, maintain the stability of your application, and even when errors occur, a meaningful feedback could be provided to your and the user.
In JavaScript, errors can be caught and handled programmatically using try catch
:
1function parseJSON(jsonString) {
2 try {
3 return JSON.parse(jsonString);
4 } catch (error) {
5 console.error("Invalid JSON:", error.message);
6 return null;
7 }
8}
9
10console.log(parseJSON('{"name": "John"}'));
11console.log(parseJSON("nsq97t)sdj9!ocix9w"));
JavaScript will first execute the try
block, if an error occurs, the catch
block will be executed. You can access the error object inside catch
, which contains detailed information regarding the error. The common practice is to log the error, so that you, the developer, will have something to work with when debugging that error.
Use strict mode
In modern JavaScript, it is usually not necessary to explicitly opt into the strict mode, as long as you are using JavaScript modules or classes, the strict mode will be activated by default. But if you are working with legacy code, it is best to declare "use strict"
at the top of your JavaScript file.
The strict mode tells JavaScript to catch errors that are usually ignored, but could cause a problem in your code. As an example, here is a function that accepts two identical input arguments:
1function identicalInputs(a, a) {
2 console.log(a);
3}
4
5identicalInputs(5, 10); // Output 10 (the last argument)
In practice, functions like this doesn't really make sense, but JavaScript will not tell you about it. This default behavior makes debugging and future maintenance very difficult.
By activating the strict mode, mistakes like this could be caught during compile, reducing potential errors and helping you write better code.
1"use strict";
2
3function identicalInputs(a, a) {
4 console.log(a);
5}
6
7identicalInputs(5, 10); // Error will be thrown
Don't repeat yourself
Whenever you feel like you are repeating yourself, it’s a signal that there may be room for improvements.
For instance, you just implemented a new feature and realize it could be used somewhere else, consider creating a function to encapsulate that logic. When you find yourself working with multiple related functions and variables, organizing them into a class can enhance the structure of your code and improve maintainability.
This practice, known as modularization, ensures code reusability, readability, and easier maintenance, making your applications more scalable and efficient.
Avoid hardcoding values
Hardcoding values makes it difficult to update your code, especially when the value is used in multiple places. Instead, you should store these values in configuration files or environment variables, and access them programmatically in your app.
For example, you can create a config.js
file where you store the configuration variables:
config.js
1export const config = {
2 port: process.env.PORT || 3000,
3 apiKey: process.env.API_KEY || "default-api-key",
4};
And import them into your app whenever you want to access them.
index.js
1import { config } from "./config.js";
2
3console.log(`Server will run on port: ${config.port}`);
4console.log(`API Key: ${config.apiKey}`);
This gives you a single source of truth for all of your variables, and when you need to change them, simply modify the config.js
file.
Similarly, you can use the environment variables to achieve the same thing.
.env
1PORT=5000
2API_KEY=my-secret-api-key
The environment variables can be accessed via process.env
.
index.js
1const port = process.env.PORT || 3000;
2const apiKey = process.env.API_KEY || "default-api-key";
3
4console.log(`Server will run on port: ${port}`);
5console.log(`API Key: ${apiKey}`);
Use syntax shortcuts
JavaScript offers a few syntax shortcuts that can help making your code cleaner and easier to read. For instance, when you need to join strings together, instead of concatenation operation, you can use the template literals, which is much easier to read:
1const name = "Alice";
2
3console.log(`Hello, ${name}!`);
Similar shortcuts include array/object destructuring, which allows you to assign items inside the array/object directly to variables:
1const user = { name: "Bob", age: 30 };
2
3const { name, age } = user;
4console.log(name, age);
The spread syntax and rest parameter. The rest parameter collects multiple function arguments into an array, and the spread syntax expands the array/object into individual elements.
1// Spread syntax
2const numbers = [1, 2, 3];
3const newNumbers = [...numbers, 4, 5];
4
5console.log(newNumbers);
1// Rest parameter
2function sum(...numbers) {
3 return numbers.reduce((sum, num) => sum + num, 0);
4}
5
6console.log(sum(1, 2, 3, 4));
And lastly, JavaScript offers many built-in methods that can be very useful in certain cases. For example, the map()
method for arrays accepts a callback function, which will be applied to every element in that array, and the result will be returned as a new array.
1const numbers = [1, 2, 3, 4, 5];
2const doubled = numbers.map((num) => num * 2);
3
4console.log(doubled);
Write modern JavaScript
JavaScript has evolved amy times over the years. New features and standards are been added constantly. You should follow the new standards and utilize the new features when creating JavaScript applications.
For example, when JavaScript was created, it used the keyword var
to declare a new variable, but that quickly become outdated due to the issue with the its scope. var
always create a global variable unless it is declared inside a function.
The JavaScript community decided to introduce const
and let
to address this issue. They both create local variables. If the variables are declared inside a block, it will not be accessible from the outside. This new behavior follows modern programming conventions, making your code easier to read, maintain, and debug.
1const name = "Charlie";
2let age = 25;
When working with modules in JavaScript, it is recommended to follow the ES modules convention. This modern standard provides a clean and consistent syntax for importing and exporting across JavaScript files.
1// Exporting with ES modules syntax
2export function add(a, b) {
3 return a + b;
4}
1// Importing with ES modules syntax
2import { add } from "./utils.js";
Modern JavaScript also provides a way for you to work with asynchronous operations using promises:
1fetch("https://api.example.com/data")
2 .then((response) => response.json())
3 .then((data) => console.log(data))
4 .catch((error) => console.error(error));
This promise syntax can be difficult to read, so JavaScript also created the async/await syntax that allows you to write asynchronous operations as if they are synchronous:
1async function fetchData() {
2 try {
3 const response = await fetch("https://api.example.com/data");
4 const data = await response.json();
5 console.log(data);
6 } catch (error) {
7 console.error("Error fetching data:", error);
8 }
9}
10fetchData();
Other modern features in JavaScript include the class notation:
1class Person {
2 constructor(name, age) {
3 this.name = name;
4 this.age = age;
5 }
6
7 greet() {
8 return `Hello, I'm ${this.name}`;
9 }
10}
Optional chaining:
1const user = { profile: { name: "Alice" } };
2console.log(user.profile?.name); // Alice
3console.log(user.address?.street); // undefined
Nullish coalescing:
1const value = null ?? "Default";
2console.log(value); // Default
Learning to use these modern features can significantly improve the efficiency and readability of your code, helping you create better applications.