Clean Code in JavaScript: Principles and Practical Examples
Clean code is not just a trendy term, but a professional necessity. In the world of JavaScript and Node.js, where projects grow and change rapidly, the ability to write clear and maintainable code distinguishes an experienced developer from a beginner. In this article, we will break down the key principles of Clean Code using specific examples from real-world practice.
Why is clean code important for a JavaScript developer?
JavaScript is a dynamically typed language, which makes it flexible but also more prone to errors. Poorly written code in Node.js can turn project maintenance into a nightmare. Clean Code helps to:
- Reduce time spent on debugging and refactoring
- Simplify onboarding new developers to the project
- Reduce the number of bugs in production
- Increase team productivity
Let's move on to specific principles and examples.
Principle 1: Meaningful variable and function names
The simplest and simultaneously the most violated rule. A variable name should answer the question "what is this?" or "what is it for?".
Bad example:
// Bad: what is d? what does fn do?const d = new Date();const fn = (a, b) => a + b;const arr = [1, 2, 3, 4];const x = arr.filter(i => i > 2);Good example:
// Good: clear without commentsconst currentDate = new Date();const sum = (firstNumber, secondNumber) => firstNumber + secondNumber;const numbers = [1, 2, 3, 4];const filteredNumbers = numbers.filter(number => number > 2);Rule for Node.js: if you are working with asynchronous operations, add suffixes indicating the type of return value: getUserDataAsync, fetchPosts.
Principle 2: Functions should be small and do one thing (KISS)
The KISS (Keep It Simple, Stupid) principle is critically important in JavaScript. A function should not be a "Swiss Army knife".
Bad example — monster function:
// Bad: one function does everythingfunction processUserData(user) { // Validation if (!user.email || !user.name) { throw new Error('Invalid user data'); } // Formatting const formattedName = user.name.trim().toLowerCase(); const formattedEmail = user.email.trim().toLowerCase(); // Saving to DB db.save({ name: formattedName, email: formattedEmail }); // Sending email emailService.sendWelcome(formattedEmail); // Logging logger.info(`User ${formattedName} processed`);}Good example — separation of concerns:
// Good: each function is responsible for one taskfunction validateUserData(user) { if (!user.email || !user.name) { throw new Error('Invalid user data'); }}
function formatUserData(user) { return { name: user.name.trim().toLowerCase(), email: user.email.trim().toLowerCase() };}
async function processUserRegistration(userData) { validateUserData(userData); const formattedUser = formatUserData(userData); await db.save(formattedUser); await emailService.sendWelcome(formattedUser.email); logger.info(`User ${formattedUser.name} processed`); return formattedUser;}Such code is easier to test, debug, and extend.
Principle 3: Avoid magic numbers and strings (DRY)
"Magic numbers" are literals whose meaning is not obvious. Extract them into constants with clear names.
Bad example:
// Bad: what is 1000? why 60?setTimeout(() => { if (user.age > 65)