Clean Code Principles: Writing Readable and Maintainable Code
Clean code reads like well-written prose—its intent is clear without explanation.
Meaningful Names
// Bad
const d = 86400; // what is d?
function calc(a: number[], b: number) { /* ... */ }
class Mgr { /* ... */ }
// Good
const SECONDS_PER_DAY = 86400;
function calculateDailyRevenue(transactions: Transaction[], taxRate: number) { /* ... */ }
class AccountManager { /* ... */ }
// Variables should reveal intent
// Bad
const list = users.filter(u => u.a > 18);
// Good
const adults = users.filter(user => user.age > LEGAL_DRINKING_AGE);
Small Functions
Functions should do one thing, do it well, and do it only.
// Bad: function does too many things
async function processUserRegistration(data: any) {
// validate
if (!data.email || !data.password) throw new Error('Missing fields');
if (!/^[^@]+@[^@]+$/.test(data.email)) throw new Error('Invalid email');
// hash password
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(data.password, salt);
// save to database
const user = await db.query('INSERT INTO users ...');
// send email
await emailClient.send({ to: data.email, subject: 'Welcome!' });
return user;
}
// Good: each function has one responsibility
async function processUserRegistration(data: RegistrationData) {
validateRegistrationData(data);
const hashedPassword = await hashPassword(data.password);
const user = await createUserAccount(data.email, hashedPassword);
await sendWelcomeEmail(user.email);
return user;
}
function validateRegistrationData(data: RegistrationData): void {
if (!data.email || !data.password) throw new ValidationError('Missing fields');
if (!isValidEmail(data.email)) throw new ValidationError('Invalid email');
}
Meaningful Comments
// Bad: comment explains WHAT (obvious from code)
// increment counter
i++;
// Bad: commented-out code
// const oldResult = computeOldWay(x);
const result = computeNewWay(x);
// Good: comment explains WHY
// Using 2^31 - 1 to avoid integer overflow in legacy 32-bit systems
const MAX_SAFE_ID = 2147483647;
// Good: clarify complex algorithm
// This implements the Levenshtein distance algorithm
// See: https://en.wikipedia.org/wiki/Levenshtein_distance
function editDistance(s1: string, s2: string): number { /* ... */ }
Error Handling
// Bad: error codes and null returns
function findUser(id: string): User | null {
// caller must check null every time
return null;
}
// Good: throw domain exceptions
class UserNotFoundError extends Error {
constructor(id: string) {
super(`User with id ${id} not found`);
this.name = 'UserNotFoundError';
}
}
async function findUser(id: string): Promise<User> {
const user = await db.users.findById(id);
if (!user) throw new UserNotFoundError(id);
return user;
}
// Good: Result type for expected failures
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
async function tryLogin(email: string, password: string): Promise<Result<User, LoginError>> {
const user = await userRepo.findByEmail(email);
if (!user) return { success: false, error: new LoginError('Invalid credentials') };
const valid = await verifyPassword(password, user.passwordHash);
if (!valid) return { success: false, error: new LoginError('Invalid credentials') };
return { success: true, data: user };
}
Don't Repeat Yourself (DRY)
// Bad: duplicated validation
function createUser(email: string) {
if (!/^[^@]+@[^@]+$/.test(email)) throw new Error('Invalid email');
// ...
}
function updateUserEmail(userId: string, email: string) {
if (!/^[^@]+@[^@]+$/.test(email)) throw new Error('Invalid email');
// ...
}
// Good: extracted function
function assertValidEmail(email: string): void {
if (!/^[^@]+@[^@]+$/.test(email)) throw new ValidationError('Invalid email');
}
Clean code is not written in one pass—it's continuously refined through refactoring.