Lesson 08

Clean Code

Write code that humans can read, maintain, and trust

Naming Functions Comments DRY YAGNI Error Handling Boy Scout Rule
💡 The Core Truth

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

Naming: Intention-Revealing Names

Names are the most important documentation in your code. A good name eliminates the need for a comment.

❌ Bad — Cryptic Abbreviations

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;
}

✅ Good — Self-Documenting

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');
}

Naming Rules

RuleBadGood
Reveal intentd, tmpelapsedDays, pendingOrders
No abbreviationsusrMgr, accRepouserManager, accountRepository
Consistent vocabularyfetch/get/retrieve mixedPick one: get everywhere
Booleans as questionsactive, statusisActive, hasPermission
Functions as verbsdata(), user()fetchData(), createUser()

Functions: Small and Focused

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.

❌ Bad — Too Much at Once

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
}

✅ Good — Composed from Small Functions

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;
}

Comments: Why, Not What

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.

❌ Bad — Stating the Obvious

// 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);

✅ Good — Explaining Intent

// 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 Do This

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.

DRY: Don't Repeat Yourself

Every piece of knowledge should have a single, unambiguous representation. Duplication means bugs are fixed in one place but not the other.

The Rule of Three

Duplicate once? Tolerable. Duplicate twice (three instances)? Extract.

1
1st time Just write it
2
2nd time Note the duplication, tolerate it
3
3rd time Extract into a shared function/module
⚠️ Wrong Abstraction is Worse Than Duplication

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.

YAGNI: You Aren't Gonna Need It

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.

Error Handling: Fail Fast

Errors should be detected early, reported clearly, and never swallowed. Silent failures are the hardest bugs to diagnose.

❌ Bad — Swallowing Errors

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
}

✅ Good — Explicit, Specific, Informative

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();
  }
}

The Boy Scout Rule

✅ "Leave the code cleaner than you found it."

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.

Clean Code Checklist

PrincipleQuick Test
NamingCan a new team member understand it without asking?
FunctionsDoes it do one thing in under 15 lines?
CommentsDoes it explain why, not what?
DRYIs this the only place this logic lives?
YAGNIDo we need this today?
Error handlingWould a failure here be visible and actionable?
Boy Scout RuleDid you leave this file a little cleaner?

🧠 Knowledge Check

What should code comments explain?