Lesson 08
Write code that humans can read, maintain, and trust
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler
Names are the most important documentation in your code. A good name eliminates the need for a comment.
const d = new Date();
const u = getU();
const calc = (a, b) => a * b - (a * b * 0.1);
function proc(lst) {
const res = [];
for (const el of lst) {
if (el.st === 'A') res.push(el);
}
return res;
}
const currentDate = new Date();
const currentUser = getCurrentUser();
const applyDiscount = (price, quantity) => {
const subtotal = price * quantity;
const discount = subtotal * DISCOUNT_RATE;
return subtotal - discount;
};
function filterActiveUsers(users) {
return users.filter(user => user.status === 'ACTIVE');
}
| Rule | Bad | Good |
|---|---|---|
| Reveal intent | d, tmp | elapsedDays, pendingOrders |
| No abbreviations | usrMgr, accRepo | userManager, accountRepository |
| Consistent vocabulary | fetch/get/retrieve mixed | Pick one: get everywhere |
| Booleans as questions | active, status | isActive, hasPermission |
| Functions as verbs | data(), user() | fetchData(), createUser() |
A function should do one thing, do it well, and do it only. If you cannot describe what a function does without using "and," it does too much.
checkPassword should not also initialize a session.function processOrder(user, items, address, coupon, notify) {
// validate items (20 lines)
// calculate totals (15 lines)
// apply coupon (10 lines)
// save to database (8 lines)
// send notification (12 lines)
// ... 65 lines total
}
function processOrder(order) {
validateItems(order.items);
const total = calculateTotal(order.items, order.coupon);
const savedOrder = saveOrder(order.userId, order.items, total);
notifyUser(order.userId, savedOrder);
return savedOrder;
}
If your code needs a comment to explain what it does, the code is not clear enough. Rewrite it. Comments should explain why — the intent, the reasoning, the trade-off.
// Increment counter by 1
counter++;
// Check if user is active
if (user.isActive) { ... }
// TODO: fix this later
// HACK: this works somehow
const result = data.map(x => x.value * 1.08);
// Tax rate is 8% per state regulation SB-1234,
// effective Jan 2025. See: legal/tax-policy.md
const TAX_RATE = 0.08;
// We retry 3 times because the payment gateway
// occasionally returns 503 during peak hours
const MAX_RETRIES = 3;
Never leave commented-out code in the codebase. That is what version control is for. Dead code creates confusion about what is active and what is not.
Every piece of knowledge should have a single, unambiguous representation. Duplication means bugs are fixed in one place but not the other.
Duplicate once? Tolerable. Duplicate twice (three instances)? Extract.
Do not force DRY if two pieces of code look similar but serve different purposes. Premature abstraction creates coupling between unrelated concerns. Duplication is cheaper than the wrong abstraction.
Do not build features, abstractions, or infrastructure you do not need right now. Speculative code has a cost: it must be maintained, tested, understood, and eventually deleted when the predicted future never arrives.
Errors should be detected early, reported clearly, and never swallowed. Silent failures are the hardest bugs to diagnose.
try {
const data = fetchData();
processData(data);
} catch (error) {
// ignore
}
// Or worse — returning null on error
function getUser(id) {
try { return db.find(id); }
catch { return null; } // caller has no idea it failed
}
function getUser(id) {
if (!id) throw new ValidationError('User ID is required');
const user = db.find(id);
if (!user) throw new NotFoundError(`User ${id} not found`);
return user;
}
// Call site handles errors explicitly
try {
const user = getUser(userId);
renderProfile(user);
} catch (error) {
if (error instanceof NotFoundError) {
renderNotFound();
} else {
logger.error('Unexpected error fetching user', { error, userId });
renderError();
}
}
Every time you touch a file, improve one small thing: rename a variable, extract a function, remove dead code, add a missing type. Over time, this compounds into a dramatically cleaner codebase — without dedicated refactoring sprints.
any you now understand| Principle | Quick Test |
|---|---|
| Naming | Can a new team member understand it without asking? |
| Functions | Does it do one thing in under 15 lines? |
| Comments | Does it explain why, not what? |
| DRY | Is this the only place this logic lives? |
| YAGNI | Do we need this today? |
| Error handling | Would a failure here be visible and actionable? |
| Boy Scout Rule | Did you leave this file a little cleaner? |
What should code comments explain?