Clean code is code that's easy to understand and easy to change. It's not about cleverness—it's about clarity. Here are principles that make code clean.
Meaningful Names#
Use Intention-Revealing Names#
1// ❌ Bad
2const d = new Date();
3const x = users.filter(u => u.a > 18);
4const list = [];
5
6// ✅ Good
7const currentDate = new Date();
8const adultUsers = users.filter(user => user.age > 18);
9const activeSubscriptions = [];Avoid Abbreviations and Single Letters#
1// ❌ Bad
2function calc(p, q, d) {
3 return p * q * (1 - d);
4}
5
6// ✅ Good
7function calculateDiscountedPrice(
8 price: number,
9 quantity: number,
10 discountRate: number,
11): number {
12 return price * quantity * (1 - discountRate);
13}Use Pronounceable Names#
1// ❌ Bad
2const genymdhms = new Date();
3const modymdhms = new Date();
4
5// ✅ Good
6const generationTimestamp = new Date();
7const modificationTimestamp = new Date();Functions#
Keep Functions Small#
1// ❌ Bad - does too many things
2async function processOrder(order: Order): Promise<void> {
3 // Validate order
4 if (!order.items.length) throw new Error('No items');
5 if (!order.customerId) throw new Error('No customer');
6 const customer = await db.customers.find(order.customerId);
7 if (!customer) throw new Error('Customer not found');
8
9 // Calculate totals
10 let subtotal = 0;
11 for (const item of order.items) {
12 const product = await db.products.find(item.productId);
13 subtotal += product.price * item.quantity;
14 }
15 const tax = subtotal * 0.1;
16 const total = subtotal + tax;
17
18 // Process payment
19 const payment = await stripe.charges.create({
20 amount: total * 100,
21 customer: customer.stripeId,
22 });
23
24 // Save order
25 await db.orders.create({ ...order, total, paymentId: payment.id });
26
27 // Send email
28 await sendEmail(customer.email, 'Order confirmation', { order });
29}
30
31// ✅ Good - single responsibility per function
32async function processOrder(order: Order): Promise<void> {
33 await validateOrder(order);
34 const total = await calculateOrderTotal(order);
35 const payment = await processPayment(order.customerId, total);
36 await saveOrder(order, total, payment.id);
37 await sendOrderConfirmation(order);
38}
39
40async function validateOrder(order: Order): Promise<void> {
41 if (!order.items.length) throw new Error('No items');
42 if (!order.customerId) throw new Error('No customer');
43
44 const customer = await db.customers.find(order.customerId);
45 if (!customer) throw new Error('Customer not found');
46}
47
48async function calculateOrderTotal(order: Order): Promise<number> {
49 let subtotal = 0;
50 for (const item of order.items) {
51 const product = await db.products.find(item.productId);
52 subtotal += product.price * item.quantity;
53 }
54 return subtotal * 1.1; // Including tax
55}Do One Thing#
1// ❌ Bad - multiple responsibilities
2function emailClients(clients: Client[]): void {
3 for (const client of clients) {
4 const record = db.find(client);
5 if (record.isActive()) {
6 email(client);
7 }
8 }
9}
10
11// ✅ Good - each function does one thing
12function getActiveClients(clients: Client[]): Client[] {
13 return clients.filter(client => {
14 const record = db.find(client);
15 return record.isActive();
16 });
17}
18
19function emailActiveClients(clients: Client[]): void {
20 const activeClients = getActiveClients(clients);
21 activeClients.forEach(client => email(client));
22}Limit Parameters#
1// ❌ Bad - too many parameters
2function createUser(
3 name: string,
4 email: string,
5 age: number,
6 address: string,
7 phone: string,
8 role: string,
9): User {
10 // ...
11}
12
13// ✅ Good - use an object
14interface CreateUserInput {
15 name: string;
16 email: string;
17 age: number;
18 address?: string;
19 phone?: string;
20 role?: string;
21}
22
23function createUser(input: CreateUserInput): User {
24 // ...
25}Comments#
Code Should Be Self-Documenting#
1// ❌ Bad - comment explains confusing code
2// Check if employee is eligible for benefits
3if (employee.type === 'ft' && employee.yrs > 1) {
4 // ...
5}
6
7// ✅ Good - code is self-explanatory
8const isFullTimeEmployee = employee.type === 'full-time';
9const hasCompletedProbation = employee.yearsEmployed > 1;
10
11if (isFullTimeEmployee && hasCompletedProbation) {
12 grantBenefits(employee);
13}Don't Comment Bad Code—Rewrite It#
1// ❌ Bad
2// Loop through array and find users over 18 who have verified
3// email and are not banned, then get their names
4const names = arr.filter(u => u.a >= 18 && u.v && !u.b).map(u => u.n);
5
6// ✅ Good
7const eligibleUsers = users.filter(isEligibleUser);
8const userNames = eligibleUsers.map(user => user.name);
9
10function isEligibleUser(user: User): boolean {
11 return user.age >= 18 && user.emailVerified && !user.isBanned;
12}Good Comments#
1// Explain why, not what
2// Using insertion sort because the array is nearly sorted (benchmarked)
3insertionSort(items);
4
5// Clarify complex business rules
6// Premium users get 20% discount, but not during sales
7// (per marketing agreement Q2 2024)
8const discount = isPremiumUser && !isSalePeriod ? 0.2 : 0;
9
10// Warn about consequences
11// WARNING: Changing this timeout breaks the payment retry logic
12// See incident #234 for context
13const PAYMENT_TIMEOUT = 30000;Error Handling#
Use Exceptions, Not Return Codes#
1// ❌ Bad
2function divide(a: number, b: number): number | null {
3 if (b === 0) return null;
4 return a / b;
5}
6
7const result = divide(10, 0);
8if (result === null) {
9 // Handle error
10}
11
12// ✅ Good
13function divide(a: number, b: number): number {
14 if (b === 0) {
15 throw new Error('Cannot divide by zero');
16 }
17 return a / b;
18}
19
20try {
21 const result = divide(10, 0);
22} catch (error) {
23 // Handle error
24}Don't Return Null#
1// ❌ Bad - caller must check for null
2function findUser(id: string): User | null {
3 return db.users.find(id) || null;
4}
5
6const user = findUser('123');
7if (user !== null) {
8 // use user
9}
10
11// ✅ Good - throw or return empty object
12function findUser(id: string): User {
13 const user = db.users.find(id);
14 if (!user) {
15 throw new UserNotFoundError(id);
16 }
17 return user;
18}
19
20// Or use Optional pattern
21function findUser(id: string): User | undefined {
22 return db.users.find(id);
23}Classes#
Single Responsibility Principle#
1// ❌ Bad - class does too much
2class User {
3 constructor(public name: string, public email: string) {}
4
5 save(): void {
6 db.users.save(this);
7 }
8
9 sendEmail(subject: string, body: string): void {
10 mailer.send(this.email, subject, body);
11 }
12
13 generateReport(): string {
14 return `User Report: ${this.name}...`;
15 }
16}
17
18// ✅ Good - separated concerns
19class User {
20 constructor(public name: string, public email: string) {}
21}
22
23class UserRepository {
24 save(user: User): void {
25 db.users.save(user);
26 }
27}
28
29class UserNotifier {
30 sendEmail(user: User, subject: string, body: string): void {
31 mailer.send(user.email, subject, body);
32 }
33}
34
35class UserReporter {
36 generateReport(user: User): string {
37 return `User Report: ${user.name}...`;
38 }
39}Keep Classes Small#
1// Classes should have a small number of instance variables
2// Methods should manipulate those variables
3// High cohesion = methods use most instance variables
4
5class OrderProcessor {
6 private order: Order;
7 private customer: Customer;
8 private paymentGateway: PaymentGateway;
9
10 constructor(order: Order, customer: Customer, gateway: PaymentGateway) {
11 this.order = order;
12 this.customer = customer;
13 this.paymentGateway = gateway;
14 }
15
16 process(): ProcessedOrder {
17 this.validateOrder();
18 const total = this.calculateTotal();
19 const payment = this.chargeCustomer(total);
20 return this.createProcessedOrder(payment);
21 }
22
23 // All methods work with instance variables
24}Formatting#
Consistent Style#
1// Pick a style and stick with it
2// Use tools: Prettier, ESLint
3
4// Vertical spacing: related code together
5function processUser(user: User): void {
6 // Validation block
7 validateName(user.name);
8 validateEmail(user.email);
9 validateAge(user.age);
10
11 // Processing block
12 const normalizedUser = normalizeUser(user);
13 const enrichedUser = enrichUser(normalizedUser);
14
15 // Persistence block
16 saveUser(enrichedUser);
17 indexUser(enrichedUser);
18}Tests#
Clean Tests Follow F.I.R.S.T#
1// Fast - tests should run quickly
2// Independent - tests should not depend on each other
3// Repeatable - tests should work in any environment
4// Self-validating - tests should have boolean output
5// Timely - tests should be written at the right time
6
7describe('OrderCalculator', () => {
8 // Arrange, Act, Assert pattern
9 it('calculates total with discount', () => {
10 // Arrange
11 const items = [
12 { price: 100, quantity: 2 },
13 { price: 50, quantity: 1 },
14 ];
15 const discount = 0.1;
16
17 // Act
18 const total = calculateTotal(items, discount);
19
20 // Assert
21 expect(total).toBe(225); // (200 + 50) * 0.9
22 });
23});The Boy Scout Rule#
"Leave the code cleaner than you found it."
Every time you touch code:
- Rename a confusing variable
- Extract a small function
- Remove dead code
- Add a clarifying comment
Small, continuous improvements compound over time.
Conclusion#
Clean code isn't about following rules mechanically—it's about empathy for the next developer (including future you). Write code that tells a story, that's easy to navigate, and that doesn't require heroics to understand.
Start small: improve one thing in every file you touch. Over time, the codebase transforms.