Clean code is the cornerstone of every successful software project. As a developer, your ability to write clean, maintainable code is crucial for the efficiency and longevity of your applications. In this article, we'll delve into ten examples of good and bad coding practices in JavaScript, highlighting the importance of writing clean code and providing actionable insights to help you level up your development skills.
Examples
- Descriptive Variable Names:
// Good:
const totalPrice = calculateTotalPrice(quantity, unitPrice);
// Bad:
const t = calcPrice(q, uP);
In a good example, variable names are descriptive and convey their purpose clearly, enhancing code readability. Conversely, the bad example uses cryptic abbreviations, making it difficult for others to understand the code's intent.
- Consistent Formatting:
// Good:
function greet(name) {
return `Hello, ${name}!`;
}
// Bad:
function greet(name){
return `Hello, ${name}!`
}
Consistent formatting improves code readability and maintainability. In a good example, proper indentation and spacing are employed, enhancing code structure. Conversely, the bad example lacks consistency, making the code harder to follow.
- Avoiding Magic Numbers:
// Good:
const TAX_RATE = 0.1;
const totalPrice = subtotal + (subtotal * TAX_RATE);
// Bad:
const totalPrice = subtotal + (subtotal * 0.1);
Magic numbers obscure the meaning of values and make code harder to maintain. In the good example, constants are used to represent magic numbers, improving code clarity and maintainability.
- Single Responsibility Principle:
// Good:
function calculateTotalPrice(quantity, unitPrice) {
return quantity * unitPrice;
}
function formatPrice(price) {
return `$${price.toFixed(2)}`;
}
// Bad:
function calculateAndFormatTotalPrice(quantity, unitPrice) {
const totalPrice = quantity * unitPrice;
return `$${totalPrice.toFixed(2)}`;
}
Functions should have a single responsibility to promote code reusability and maintainability. In the good example, each function performs a specific task, adhering to the Single Responsibility Principle. Conversely, the bad example violates this principle by combining multiple responsibilities into one function.
- Error Handling:
// Good:
function fetchData(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
console.error('Error fetching data:', error);
throw error;
});
}
// Bad:
function fetchData(url) {
return fetch(url)
.then(response => response.json())
.catch(error => console.error(error));
}
Proper error handling improves code robustness and helps identify and resolve issues more effectively. In the good example, errors are handled gracefully, providing meaningful feedback to developers. Conversely, the bad example lacks comprehensive error handling, potentially leading to silent failures.
- Comments and Documentation:
// Good:
// Calculate the total price based on quantity and unit price
function calculateTotalPrice(quantity, unitPrice) {
return quantity * unitPrice;
}
// Bad:
function calculateTotalPrice(quantity, unitPrice) {
// calculate total price
return quantity * unitPrice;
}
Comments and documentation enhance code understandability and facilitate collaboration among developers. In a good example, clear comments describe the purpose of the function, aiding in code comprehension. Conversely, the bad example provides vague comments that add little value.
- Proper Modularity:
// Good:
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// Bad:
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
Modular code promotes reusability and maintainability by organizing functionality into cohesive units. In the good example, functions are properly encapsulated and exported, facilitating code reuse. Conversely, the bad example lacks modularization, making it harder to manage and scale.
- DRY Principle (Don't Repeat Yourself):
// Good:
const greeting = 'Hello';
function greet(name) {
return `${greeting}, ${name}!`;
}
// Bad:
function greet(name) {
const greeting = 'Hello';
return `${greeting}, ${name}!`;
}
Repetitive code increases the risk of errors and makes maintenance challenging. In a good example, repetitive strings are extracted into a constant, adhering to the DRY principle and improving code maintainability. Conversely, the bad example redundantly defines the greeting within the function.
- Meaningful Function Names:
// Good:
function calculateArea(radius) {
return Math.PI * radius ** 2;
}
// Bad:
function calc(r) {
return Math.PI * r ** 2;
}
Function names should accurately reflect their purpose to enhance code readability. In the good example, the function name "calculateArea" clearly indicates its functionality. Conversely, the bad example uses a cryptic abbreviation ("calc"), making it unclear what the function does.
- Testability:
// Good:
function sum(a, b) {
return a + b;
}
module.exports = sum;
// Bad:
function sum(a, b) {
console.log(a + b);
}
Writing testable code facilitates automated testing, ensuring code reliability and stability. In the good example, the function is exported for testing purposes, enabling easy test setup and execution. Conversely, the bad example contains side effects (console.log), making it challenging to test the function's behavior.
- Proper Use of Data Structures:
// Good:
const studentGrades = [90, 85, 95, 88];
const averageGrade = studentGrades.reduce((total, grade) => total + grade, 0) / studentGrades.length;
// Bad:
const grade1 = 90;
const grade2 = 85;
const grade3 = 95;
const grade4 = 88;
const averageGrade = (grade1 + grade2 + grade3 + grade4) / 4;
Utilizing appropriate data structures enhances code readability and maintainability. In the good example, an array is used to store student grades, allowing for easy manipulation and calculation. Conversely, the bad example relies on individual variables, leading to repetitive and error-prone code.
- Handling Asynchronous Operations:
// Good:
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Error fetching data:', error);
throw error;
}
}
// Bad:
function fetchData(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
console.error('Error fetching data:', error);
throw error;
});
}
Proper handling of asynchronous operations ensures code reliability and robustness. In a good example, async/await syntax is used to simplify asynchronous code and handle errors gracefully. Conversely, the bad example uses nested promises, leading to callback hell and decreased code readability.
- Dependency Management:
// Good:
import { format } from 'date-fns';
// Bad:
const dateFns = require('date-fns');
Effective dependency management promotes code modularity and scalability. In a good example, ES6 import syntax is used to import only the required functionality from the 'date-fns' library, reducing unnecessary imports and improving performance. Conversely, the bad example uses CommonJS require syntax, which imports the entire 'date-fns' module, potentially bloating the application bundle.
- Performance Optimization:
// Good:
const sortedNumbers = [5, 2, 8, 1, 9];
sortedNumbers.sort((a, b) => a - b);
// Bad:
const unsortedNumbers = [5, 2, 8, 1, 9];
const sortedNumbers = unsortedNumbers.sort();
Optimizing code for performance ensures efficient execution and enhances user experience. In a good example, the sort() method is called with a custom comparison function to sort numbers in ascending order, resulting in better performance compared to the default sorting algorithm. Conversely, the bad example relies on the default sorting algorithm, which may not be the most efficient for numerical arrays.
- Proper Error Handling in Node.js APIs:
// Good:
app.get('/user/:id', async (req, res) => {
try {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
console.error('Error fetching user:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Bad:
app.get('/user/:id', async (req, res) => {
const user = await getUserById(req.params.id);
if (!user) {
res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
Proper error handling is crucial in Node.js APIs to ensure robustness and reliability. In the good example, errors are caught and logged, and appropriate HTTP status codes are returned to the client. Conversely, the bad example fails to handle errors, potentially resulting in unhandled promise rejections and inconsistent error responses.
- Efficient File System Operations:
// Good:
const fs = require('fs').promises;
async function readFile(filePath) {
try {
const data = await fs.readFile(filePath, 'utf-8');
console.log(data);
} catch (error) {
console.error('Error reading file:', error);
}
}
// Bad:
const fs = require('fs');
function readFile(filePath) {
fs.readFile(filePath, 'utf-8', (error, data) => {
if (error) {
console.error('Error reading file:', error);
return;
}
console.log(data);
});
}
Using promises in file system operations enhances code readability and simplifies error handling. In a good example, fs.promises.readFile() is used to read a file asynchronously, and errors are handled using try-catch. Conversely, the bad example uses the callback-based approach, which can lead to callback hell and less readable code.
- Efficient Memory Management:
// Good:
const stream = fs.createReadStream('bigfile.txt');
stream.pipe(response);
// Bad:
fs.readFile('bigfile.txt', (error, data) => {
if (error) {
console.error('Error reading file:', error);
return;
}
response.write(data);
});
Using streams for large file processing in Node.js conserves memory and improves performance. In a good example, fs.createReadStream() and stream.pipe() are used to efficiently stream data from a file to an HTTP response. Conversely, the bad example reads the entire file into memory before writing it to the response, which can lead to memory issues for large files.
- Proper Module Exporting and Importing:
// Good:
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
// Bad:
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
Consistent module exporting and importing practices improve code readability and maintainability. In the good example, module.exports is used to export an object containing functions, while in the bad example, exports are used directly. Although both methods work, sticking to one convention enhances code consistency.
- Asynchronous Control Flow:
// Good:
async function processItems(items) {
for (const item of items) {
await processItem(item);
}
}
// Bad:
function processItems(items) {
items.forEach(item => {
processItem(item);
});
}
Proper asynchronous control flow ensures that operations are executed sequentially or concurrently as needed. In a good example, an async function is used with a for...of the loop to process items sequentially, awaiting each operation. Conversely, the bad example uses forEach, which does not handle asynchronous operations well and may lead to unexpected behavior.
Top comments (12)
I have some issues with your examples:
First, in your comments example "Calculate the total price based on quantity and unit price" just simply restates what the function already defines anyways. I'd consider this to be a bad comment. A good comment should state something about the context, about the 'why' this piece of code exists. What it does should be as clear as possible by it's definition.
Also your "Asynchronous Control Flow" example is basically just making the async calls synchronous without good reason. According to the function call whatever the asynchronous code does seems not to be dependent on a previous function call. (If it does, you'd probably have to rethink what you do, because it should probably be batched together into something like a transaction). So, based on that why limit the async code to only one execution at once?
Is probably closer to what you want. Also - as @thenickest mentioned - it's missing error handling since every async execution could get rejected.
Good I was about to comment about the comments section, but you was faster 😃. A redundant comment could became a problem because with time people tend to update the code and forget to update the comments, so I would avoid comments on code and try to make the declarations more comprehensible as possible.
But @mahabubr, very good article anyway, congrats.
thank you for share your prospective
Your 'Performance Optimization' example is wrong / nonsensical....
Great post! In my experience we need more people taking clean code seriously and helping raise the tide 😁
Often when I talk to people about these type of things I get responses like “I don’t care about writing beautiful code, we have real work to do”, so I feel like it’s always important to focus on why these principles are valuable. They help us build systems which can adapt to change more easily. I find that most of the value comes from just putting in a little effort with SRP and variable naming ✌️
I don't agree with the magic numbers example. I don't understand how using a constant violates clean code principles. In your bad example, you use TAX_RATE. If the tax rate changed, you would have to go through your function and update it in multiple spots. In a large code base, there is the risk of missing a value.
I agree with the other devs regarding your comment examples. I personally only add comments to add clarity for a decision made that isn't easy to articulate in the code itself. The code itself would be written in a way that explains its function.
Since taxrate is stored in a constant variable, that will only take a single change, change the taxRate variable to another value and that's all, my opinion though.
Please change the name of this page to "Mastering Clean Code: Essential Practices for Javascript Developers". That would make it a more clear and descriptive name.
Maybe put the language version in the text at the top of the page. So instead of "JavaScript", it could say, "JavaScript (ECMAscript 2023)". That may help future readers understand the context, depending on future additions to the language.
Thank you for writing this, Mahabubur Rahman, and commenters.
I personally like seeing developers applying clean code principles, so I hope my feedback finds you well. Your source code lacks some highlighting. Would be neat to see that next time.
Also error handling and asynchronous are not really consistent. The then/catch example appears to be ok for errors but not for asynchronous operations? It also hides the fact that there can be good reasons to choose one over the other. I agree that it can get messy if nested too much, but this is not shown in your example.
thank you for share your prospective
What a great post! Every developer should follow these instructions. Clean code is the best prerequisite for a great project.
Thanks, now I know how to write clean code
Some comments may only be visible to logged-in visitors. Sign in to view all comments.