Clean Code
Stage 3 · Testing, Quality & Craft · B.U.I.L.D. letter: U
You shipped something that works. You're proud of it. Then three weeks later you open the file and think: who wrote this? The answer, heartbreakingly, is you. Clean code is the discipline of writing code that Future You — or anyone else — can actually understand without a decoder ring.
⚠️ The vibe trap
Vibe coding gets things running fast, and that's a genuine superpower. But AI-assisted speed can leave behind a trail of data2, tempVal, and 80-line functions that nobody — not you, not a teammate, not even the AI helping you refactor — can reason about confidently. Working code is the floor, not the ceiling. The gap between "it runs" and "it can be safely changed" is exactly where production bugs are born.
🏷️ Naming: say what you mean
The single highest-leverage thing you can do for a codebase is name things well. A good name is a free comment.
Before — what even is this?
function calc(x, y, z) {
const t = x * y;
const r = t - z;
return r > 0 ? r : 0;
}
After — reads like a sentence
function calculateDiscountedPrice(basePrice, quantity, discountAmount) {
const subtotal = basePrice * quantity;
const discountedTotal = subtotal - discountAmount;
return Math.max(discountedTotal, 0);
}
Mental model: Every name is a promise to the next reader. x promises nothing. basePrice promises everything you need to know.
Why it matters: Misread variable names are a top source of off-by-one bugs and wrong-unit bugs. A function named getUser that actually creates a user if it doesn't exist is a landmine.
Common mistake: Abbreviating to save keystrings. usrPrfDt is not faster to type than userProfileData — your editor autocompletes both. It is slower to read, every single time.
🎯 Functions that do one thing
A function should do one thing, do it well, and do it only. If you struggle to name a function without the word "and," it's doing two things.
Before — a function wearing too many hats
function handleSubmit(formData) {
// validate
if (!formData.email.includes('@')) {
alert('Bad email');
return;
}
// transform
formData.email = formData.email.toLowerCase().trim();
formData.createdAt = new Date().toISOString();
// save
fetch('/api/users', { method: 'POST', body: JSON.stringify(formData) })
.then(res => res.json())
.then(user => {
localStorage.setItem('currentUser', JSON.stringify(user));
window.location.href = '/dashboard';
});
}
After — each function owns one responsibility
function isValidEmail(email) {
return email.includes('@');
}
function normalizeFormData(formData) {
return {
...formData,
email: formData.email.toLowerCase().trim(),
createdAt: new Date().toISOString(),
};
}
async function createUser(formData) {
const res = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(formData),
});
return res.json();
}
async function handleSubmit(formData) {
if (!isValidEmail(formData.email)) {
alert('Bad email');
return;
}
const user = await createUser(normalizeFormData(formData));
localStorage.setItem('currentUser', JSON.stringify(user));
window.location.href = '/dashboard';
}
Mental model: Small functions are testable in isolation. The big one above is nearly impossible to unit-test — you'd have to mock fetch, localStorage, alert, and window.location all at once.
Why it matters: When a bug lands in a 5-line function, you find it in 30 seconds. When it lands in a 60-line function, you spend an afternoon.
Common mistake: "I'll split it later." You won't. Write small the first time.
🚪 Guard clauses: escape early, stay flat
Deep nesting is cognitive debt. Every level of indentation asks your brain to hold one more thing in memory. Guard clauses (also called early returns) keep the happy path at the leftmost indent.
Before — the pyramid of doom
function processOrder(order) {
if (order) {
if (order.items.length > 0) {
if (order.paymentMethod) {
if (order.shippingAddress) {
// actual logic buried four levels deep
return fulfillOrder(order);
} else {
return { error: 'No shipping address' };
}
} else {
return { error: 'No payment method' };
}
} else {
return { error: 'Empty cart' };
}
} else {
return { error: 'No order' };
}
}
After — guard early, then get to the point
function processOrder(order) {
if (!order) return { error: 'No order' };
if (order.items.length === 0) return { error: 'Empty cart' };
if (!order.paymentMethod) return { error: 'No payment method' };
if (!order.shippingAddress) return { error: 'No shipping address' };
return fulfillOrder(order);
}
Mental model: Guards handle the "nope" cases upfront so the body of the function can assume everything is fine. It's the same logic; the structure just telegraphs the intent.
Why it matters: Reviewers can read the error cases in 4 lines without holding nested context. AI assistants also refactor flat code far more reliably than deeply nested code.
Common mistake: Thinking multiple returns are bad style. They're not — they're a feature. One-return rules come from languages where you had to manually free memory; JavaScript is not that language.
🔢 No magic numbers — name your constants
A magic number is any literal value whose meaning isn't obvious from context. 0.08 means nothing. SALES_TAX_RATE means everything.
Before — what's 7? What's 0.15?
function getShippingCost(orderTotal, itemCount) {
if (orderTotal > 50) return 0;
if (itemCount > 7) return orderTotal * 0.15;
return 5.99;
}
After — every value is self-documenting
const FREE_SHIPPING_THRESHOLD = 50;
const BULK_ITEM_THRESHOLD = 7;
const BULK_SHIPPING_RATE = 0.15;
const STANDARD_SHIPPING_COST = 5.99;
function getShippingCost(orderTotal, itemCount) {
if (orderTotal > FREE_SHIPPING_THRESHOLD) return 0;
if (itemCount > BULK_ITEM_THRESHOLD) return orderTotal * BULK_SHIPPING_RATE;
return STANDARD_SHIPPING_COST;
}
Mental model: Constants are change-points. When the business says "raise the free shipping threshold to $75," you change one line, not five scattered ones.
Why it matters: Magic numbers are silent duplicates. 50 appears in shipping logic, in a test, and in a marketing banner — three places that all need to stay in sync. A named constant unifies them.
Common mistake: Named constants for things that are obvious: const ONE = 1 or const EMPTY_STRING = ''. Reserve names for values that carry domain meaning.
💬 Comments that explain why, not what
If a comment is just re-stating what the code does in English, delete it — the code already says that. Comments earn their keep when they explain why the code does something non-obvious.
Before — comment restates the code
// Loop through users
for (const user of users) {
// Check if user is active
if (user.isActive) {
// Send email
sendWelcomeEmail(user.email);
}
}
After — comment explains the business rule
for (const user of users) {
// Only active users: inactive accounts opted out of marketing in Dec 2024
if (user.isActive) {
sendWelcomeEmail(user.email);
}
}
Mental model: A future developer reading the "what" comment learns nothing they couldn't read from the code itself. The "why" comment saves them from accidentally removing that guard and reintroducing a compliance issue.
Why it matters: Comments that repeat the code rot — when the code changes and the comment doesn't, the comment actively misleads. Comments that explain intent stay relevant even when the implementation evolves.
Common mistake: Leaving commented-out code "just in case." That's what git is for. Dead code in comments creates confusion about whether it still matters. Delete it.
🛠️ Your mission
Find one function in your current project that makes you wince — you know the one. Work through it in this order:
- Rename every variable and parameter to reveal intent. No abbreviations, no single letters.
- Guard clauses — flatten any nesting deeper than two levels with early returns.
- Constants — extract every magic number and magic string to a named constant.
- Dead code — delete all commented-out blocks and unreachable code.
- Comments — strip restating comments; add one only if a non-obvious business rule needs explaining.
- Split — if it still does two things, split it.
Run your tests after each step.
✅ You're done when…
- Your refactored function passes the Code Review Rubric: intention-revealing names, no magic numbers, max two levels of nesting, no dead code, comments explain why not what
- Every renamed variable and parameter reads like a plain-English description of what it holds
- Guard clauses handle all error/edge cases before the happy path
- At least one magic number or magic string has been replaced with a named constant
- All commented-out code blocks are deleted (not just collapsed)
- Your existing tests still pass — or you updated them to reflect the correct new behavior
➡️ Next: Code Smells & Refactoring. Build It Right, Or Don't Build It At All. 🏛️