How to Write Clean Code in JavaScript
On this page
On this page
Writing clean, maintainable JavaScript code is essential for building scalable applications and working effectively in teams. This guide covers essential principles and patterns for writing professional JavaScript code, drawing from Clean Code principles and JavaScript best practices.
Naming Conventions
Clear, descriptive names make code self-documenting. Follow consistent naming conventions:
JavaScript naming conventions:
// Variables and functions: use camelCase
let userName = "John";
let isActive = true;
function calculateTotal() {}
function getUserById() {}
// Constants: use UPPER_SNAKE_CASE
const MAX_CONNECTIONS = 100;
const API_BASE_URL = "https://api.example.com";
const DEFAULT_TIMEOUT = 5000;
// Classes: use PascalCase
class UserAccount {}
class DatabaseConnection {}
class HTTPRequestHandler {}
// Private methods: prefix with underscore (convention)
class MyClass {
_privateMethod() {
// Internal method
}
}
// Boolean variables: use is/has/should prefix
let isLoggedIn = true;
let hasPermission = false;
let shouldUpdate = true;
// Bad naming
let d = {}; // What is this?
let x = 5; // What does x represent?
// Good naming
let userData = {};
let maxRetries = 5;Function Design Principles
Functions should be small, focused, and do one thing well (Single Responsibility Principle):
Good function design:
// Bad: Function does too many things
function processUser(user) {
if (!user) return null;
if (!user.email) return null;
if (!user.email.includes("@")) return null;
user.email = user.email.toLowerCase();
user.name = user.name.trim();
user.name = user.name.charAt(0).toUpperCase() + user.name.slice(1);
// ... more processing
return user;
}
// Good: Small, focused functions
function validateEmail(email) {
return email && email.includes("@");
}
function normalizeEmail(email) {
return email.toLowerCase().trim();
}
function capitalizeName(name) {
return name.trim().charAt(0).toUpperCase() + name.slice(1);
}
function processUser(user) {
if (!user) return null;
if (!validateEmail(user.email)) return null;
return {
...user,
email: normalizeEmail(user.email),
name: capitalizeName(user.name)
};
}
// Functions should have descriptive names
// Bad
function calc(x, y) {
return x * y;
}
// Good
function calculateTotalPrice(price, quantity) {
return price * quantity;
}Avoid deep nesting - use early returns:
// Bad: Deep nesting
function processOrder(order) {
if (order) {
if (order.items) {
if (order.items.length > 0) {
if (order.user) {
if (order.user.isActive) {
return processPayment(order);
} else {
return "User inactive";
}
} else {
return "User not found";
}
} else {
return "No items";
}
} else {
return "Invalid order";
}
} else {
return "Order required";
}
}
// Good: Early returns (guard clauses)
function processOrder(order) {
if (!order) return "Order required";
if (!order.items || order.items.length === 0) return "No items";
if (!order.user) return "User not found";
if (!order.user.isActive) return "User inactive";
return processPayment(order);
}Avoid Magic Numbers and Strings
Use named constants:
// Bad: Magic numbers
function calculateDiscount(price) {
return price * 0.1; // What is 0.1?
}
if (user.age >= 18) { // Why 18?
// ...
}
// Good: Named constants
const DISCOUNT_RATE = 0.1;
const LEGAL_AGE = 18;
function calculateDiscount(price) {
return price * DISCOUNT_RATE;
}
if (user.age >= LEGAL_AGE) {
// ...
}
// Bad: Magic strings
if (status === "active") { // Typo risk
// ...
}
// Good: Constants
const USER_STATUS = {
ACTIVE: "active",
INACTIVE: "inactive",
PENDING: "pending"
};
if (status === USER_STATUS.ACTIVE) {
// ...
}Error Handling
Proper error handling makes code robust and easier to debug:
Good error handling:
// Use try-catch for async operations
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Failed to fetch user ${userId}:`, error);
throw error; // Re-throw or handle appropriately
}
}
// Validate inputs
function divide(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Both arguments must be numbers");
}
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
// Custom error classes
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateEmail(email) {
if (!email || !email.includes("@")) {
throw new ValidationError("Invalid email address");
}
return true;
}Code Organization
Organize code logically:
// Good: Group related functionality
// User utilities
function validateUser(user) { /* ... */ }
function formatUserName(user) { /* ... */ }
function getUserDisplayName(user) { /* ... */ }
// API utilities
async function fetchUsers() { /* ... */ }
async function createUser(userData) { /* ... */ }
async function updateUser(userId, updates) { /* ... */ }
// Constants at the top
const API_BASE_URL = "https://api.example.com";
const DEFAULT_PAGE_SIZE = 20;
// Use modules/ES6 imports for better organization
// userUtils.js
export function validateUser(user) { /* ... */ }
export function formatUserName(user) { /* ... */ }
// api.js
import { validateUser } from "./userUtils.js";
export async function createUser(userData) {
if (!validateUser(userData)) {
throw new Error("Invalid user data");
}
// ...
}Modern JavaScript Patterns
Use modern JavaScript features:
// Use const/let instead of var
const API_URL = "https://api.example.com";
let currentUser = null;
// Use arrow functions for callbacks
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
// Use destructuring
const { name, email } = user;
const [first, second, ...rest] = items;
// Use spread operator
const updatedUser = { ...user, age: 31 };
const allItems = [...items1, ...items2];
// Use template literals
const message = `Hello, ${user.name}! Welcome to ${appName}.`;
// Use optional chaining (ES2020)
const city = user?.address?.city;
const email = user?.contact?.email ?? "No email";
// Use nullish coalescing (ES2020)
const timeout = settings.timeout ?? 5000;
// Use async/await instead of callbacks
async function fetchData() {
try {
const response = await fetch(API_URL);
const data = await response.json();
return data;
} catch (error) {
console.error("Error:", error);
throw error;
}
}Learning Resources
Continue learning about clean JavaScript code:
- Clean Code Book: Robert C. Martin's "Clean Code" (applies to JavaScript)
- JavaScript.info: Modern JavaScript best practices
- MDN Web Docs: JavaScript Guide
- FreeCodeCamp: JavaScript articles and tutorials
Share Your Feedback
Your thoughts help me improve my content.
Comments and Documentation
Good code is self-documenting, but comments are valuable for explaining "why" not "what":
Good vs bad comments: