Clean Code Principles
Quick reference guide for writing clean, maintainable code.
What is Clean Code?
Clean code is code that is easy to understand and easy to change:
> "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." - Martin Fowler
Meaningful Names
Use intention-revealing names that explain what something does:
Bad Examples
// ❌ BAD: Unclear, abbreviated, meaningless
let d; // elapsed time in days
let list1;
function getData() {}
const a = 5;
let temp;
Good Examples
// ✅ GOOD: Clear, descriptive, purposeful
let elapsedTimeInDays;
let activeUsers;
function getUserProfile() {}
const MAX_LOGIN_ATTEMPTS = 5;
let userTemporaryPassword;
Naming Rules
// ========== Use Pronounceable Names ==========
// ❌ BAD
const yyyymmdstr = moment().format("YYYY/MM/DD");
// ✅ GOOD
const currentDate = moment().format("YYYY/MM/DD");
// ========== Use Searchable Names ==========
// ❌ BAD
setTimeout(doSomething, 86400000); // What is 86400000?
// ✅ GOOD
const MILLISECONDS_IN_A_DAY = 86400000;
setTimeout(doSomething, MILLISECONDS_IN_A_DAY);
// ========== Avoid Mental Mapping ==========
// ❌ BAD
locations.forEach((l) => {
doStuff();
doSomeOtherStuff();
// Wait, what is `l` again?
dispatch(l);
});
// ✅ GOOD
locations.forEach((location) => {
doStuff();
doSomeOtherStuff();
dispatch(location);
});
Functions
Functions should be small and do one thing:
Function Size
// ❌ BAD: Too long, does multiple things
function processUser(user) {
// Validate user
if (!user.email) return false;
if (!user.name) return false;
// Save to database
const result = db.save(user);
// Send email
sendEmail(user.email, "Welcome!");
// Log activity
logger.info("User processed");
// Update cache
cache.set(user.id, user);
return result;
}
// ✅ GOOD: Small, single responsibility
function validateUser(user) {
return user.email && user.name;
}
function saveUser(user) {
return db.save(user);
}
function notifyUser(user) {
sendEmail(user.email, "Welcome!");
}
function logUserActivity(action) {
logger.info(`User ${action}`);
}
function processUser(user) {
if (!validateUser(user)) return false;
const result = saveUser(user);
notifyUser(user);
logUserActivity("processed");
return result;
}
Function Arguments
// ❌ BAD: Too many arguments
function createUser(name, email, age, country, city, phone, address) {
// ...
}
// ✅ GOOD: Use object parameter
function createUser(userDetails) {
const { name, email, age, country, city, phone, address } = userDetails;
// ...
}
// ========== Avoid Flag Arguments ==========
// ❌ BAD
function createFile(name, isPublic) {
if (isPublic) {
// create public file
} else {
// create private file
}
}
// ✅ GOOD
function createPublicFile(name) {
// create public file
}
function createPrivateFile(name) {
// create private file
}
Descriptive Function Names
// ❌ BAD: Vague names
function process() {}
function handle() {}
function do() {}
// ✅ GOOD: Clear action and purpose
function processPayment() {}
function handleUserLogin() {}
function validateEmailFormat() {}
function calculateTotalPrice() {}
function sendWelcomeEmail() {}
Don't Repeat Yourself (DRY)
Eliminate duplication by extracting common code:
// ❌ BAD: Duplicated code
function calculateDiscountForStudent(price) {
const discount = price * 0.1;
return price - discount;
}
function calculateDiscountForSenior(price) {
const discount = price * 0.15;
return price - discount;
}
function calculateDiscountForMilitary(price) {
const discount = price * 0.2;
return price - discount;
}
// ✅ GOOD: Reusable function
function calculateDiscount(price, discountRate) {
const discount = price * discountRate;
return price - discount;
}
const DISCOUNT_RATES = {
STUDENT: 0.1,
SENIOR: 0.15,
MILITARY: 0.2
};
calculateDiscount(100, DISCOUNT_RATES.STUDENT);
calculateDiscount(100, DISCOUNT_RATES.SENIOR);
Comments
Code should be self-explanatory. Use comments only when necessary:
Bad Comments
// ❌ BAD: Obvious, redundant comments
// Get the user
const user = getUser();
// Loop through items
items.forEach(item => {
// Process the item
process(item);
});
// Increment i
i++;
Good Comments
// ✅ GOOD: Explain WHY, not WHAT
// Using regex instead of string methods for 10x performance improvement
const isValid = /^[a-z0-9]+$/i.test(input);
// Workaround for IE11 bug where Date.now() returns incorrect value
const timestamp = Date.now() || new Date().getTime();
// TODO: Refactor this when API v2 is ready
// WARNING: This operation is expensive, consider caching
// HACK: Temporary fix for legacy system, remove after migration
Self-Documenting Code
// ❌ BAD: Needs comment to explain
// Check if user has permission
if (user.role === 'admin' || user.role === 'moderator') {
// Allow access
}
// ✅ GOOD: Code explains itself
function hasModeratorPermission(user) {
const moderatorRoles = ['admin', 'moderator'];
return moderatorRoles.includes(user.role);
}
if (hasModeratorPermission(user)) {
// Allow access
}
Error Handling
Handle errors gracefully and consistently:
// ❌ BAD: Silent failures
function getUser(id) {
try {
return db.findUser(id);
} catch (error) {
return null; // What went wrong?
}
}
// ✅ GOOD: Descriptive error handling
function getUser(id) {
try {
return db.findUser(id);
} catch (error) {
logger.error(`Failed to fetch user ${id}:`, error);
throw new UserNotFoundError(`User with ID ${id} not found`);
}
}
// ========== Use Custom Errors ==========
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
class DatabaseError extends Error {
constructor(message) {
super(message);
this.name = 'DatabaseError';
}
}
// ========== Handle Different Error Types ==========
try {
processPayment(order);
} catch (error) {
if (error instanceof ValidationError) {
return res.status(400).json({ error: error.message });
}
if (error instanceof DatabaseError) {
return res.status(500).json({ error: 'Database error occurred' });
}
throw error; // Unexpected error, rethrow
}
Code Organization
Organize code logically and consistently:
File Structure
src/
├── components/ # UI components
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx
│ │ └── index.ts
│ └── Card/
├── services/ # Business logic
│ ├── userService.ts
│ └── paymentService.ts
├── utils/ # Utility functions
│ ├── formatters.ts
│ └── validators.ts
├── types/ # Type definitions
└── constants/ # App constants
Code Grouping
// ✅ GOOD: Group related code together
class UserService {
// Constants
private readonly MAX_ATTEMPTS = 3;
private readonly CACHE_TTL = 3600;
// Constructor
constructor(private db: Database) {}
// Public methods
async getUser(id: string) {
return this.fetchUserFromCache(id) || this.fetchUserFromDB(id);
}
async createUser(data: UserData) {
this.validateUserData(data);
return this.saveUser(data);
}
// Private helper methods
private async fetchUserFromCache(id: string) {
// ...
}
private async fetchUserFromDB(id: string) {
// ...
}
private validateUserData(data: UserData) {
// ...
}
}
SOLID Principles
Single Responsibility Principle (SRP)
A class should have one reason to change:
// ❌ BAD: Multiple responsibilities
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
save() {
// Database logic
db.save(this);
}
sendEmail(message) {
// Email logic
emailService.send(this.email, message);
}
generateReport() {
// Reporting logic
return `Report for ${this.name}`;
}
}
// ✅ GOOD: Single responsibility
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRepository {
save(user) {
db.save(user);
}
}
class EmailService {
sendToUser(user, message) {
emailService.send(user.email, message);
}
}
class ReportGenerator {
generateUserReport(user) {
return `Report for ${user.name}`;
}
}
Open/Closed Principle (OCP)
Open for extension, closed for modification:
// ❌ BAD: Need to modify for new types
class PaymentProcessor {
process(payment, type) {
if (type === 'credit') {
// Process credit card
} else if (type === 'paypal') {
// Process PayPal
} else if (type === 'bitcoin') {
// Process Bitcoin
}
}
}
// ✅ GOOD: Extend without modifying
class PaymentProcessor {
constructor(private paymentMethod: PaymentMethod) {}
process(payment) {
this.paymentMethod.processPayment(payment);
}
}
class CreditCardPayment implements PaymentMethod {
processPayment(payment) {
// Process credit card
}
}
class PayPalPayment implements PaymentMethod {
processPayment(payment) {
// Process PayPal
}
}
Dependency Inversion Principle (DIP)
Depend on abstractions, not concretions:
// ❌ BAD: Direct dependency on concrete class
class UserService {
constructor() {
this.database = new MySQLDatabase(); // Tightly coupled
}
getUser(id) {
return this.database.find(id);
}
}
// ✅ GOOD: Depend on interface/abstraction
class UserService {
constructor(private database: Database) {} // Injected dependency
getUser(id) {
return this.database.find(id);
}
}
// Can easily swap implementations
const mysqlDb = new MySQLDatabase();
const mongoDb = new MongoDatabase();
const userService = new UserService(mysqlDb); // or mongoDb
Testing
Write testable code with clear test cases:
// ✅ GOOD: Small, testable functions
function calculateTotalPrice(items, discountRate = 0) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const discount = subtotal * discountRate;
return subtotal - discount;
}
// Test cases
describe('calculateTotalPrice', () => {
it('should calculate total without discount', () => {
const items = [{ price: 10 }, { price: 20 }];
expect(calculateTotalPrice(items)).toBe(30);
});
it('should apply discount correctly', () => {
const items = [{ price: 100 }];
expect(calculateTotalPrice(items, 0.1)).toBe(90);
});
it('should handle empty items', () => {
expect(calculateTotalPrice([])).toBe(0);
});
});
Code Smells to Avoid
Long Methods
// ❌ BAD: Method doing too much
function processOrder(order) {
// 100+ lines of code
}
// ✅ GOOD: Break into smaller functions
function processOrder(order) {
validateOrder(order);
calculateTotal(order);
applyDiscount(order);
processPayment(order);
sendConfirmation(order);
}
Magic Numbers
// ❌ BAD: Unexplained numbers
if (user.age > 18 && user.status === 2) {
// ...
}
// ✅ GOOD: Named constants
const LEGAL_AGE = 18;
const STATUS_ACTIVE = 2;
if (user.age > LEGAL_AGE && user.status === STATUS_ACTIVE) {
// ...
}
Deep Nesting
// ❌ BAD: Deeply nested
function processUser(user) {
if (user) {
if (user.isActive) {
if (user.hasPermission) {
if (user.email) {
// Do something
}
}
}
}
}
// ✅ GOOD: Early returns
function processUser(user) {
if (!user) return;
if (!user.isActive) return;
if (!user.hasPermission) return;
if (!user.email) return;
// Do something
}
Best Practices
Consistent Formatting
// ✅ GOOD: Consistent style
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 },
];
function getActiveUsers(users) {
return users.filter(user => user.isActive);
}
Meaningful Variable Names
// ❌ BAD
const x = users.filter(u => u.a > 18);
// ✅ GOOD
const adultUsers = users.filter(user => user.age > 18);
Pure Functions
// ❌ BAD: Has side effects
let total = 0;
function addToTotal(value) {
total += value; // Modifies external state
}
// ✅ GOOD: Pure function
function calculateTotal(currentTotal, value) {
return currentTotal + value; // No side effects
}
Use Modern Language Features
// ❌ BAD: Old style
var result = [];
for (var i = 0; i < items.length; i++) {
if (items[i].active) {
result.push(items[i].name);
}
}
// ✅ GOOD: Modern approach
const result = items
.filter(item => item.active)
.map(item => item.name);