Craft of code
Naming and comments
Names encode role and units. Functions do one thing at one level. Comments explain why, not what.
Names are the first documentation the next engineer reads. A good name encodes role and units, not implementation trivia. Functions should do one thing at one level of abstraction. Comments should explain why, not narrate what.
In a shared codebase, local elegance that fights the house style is a tax on every reader. Match existing patterns for folder layout, error types, logging, and how async work is structured — even if you would design it differently greenfield. Consistency lowers cognitive load.
When the existing pattern is actively harmful, fix it in a focused change (see refactoring and legacy), do not invent a second style beside it. Two competing conventions are worse than one imperfect one.
Walking through names, then a rewrite
| Weak | Stronger | Why |
|---|---|---|
d | delayMs | Units belong in the name |
handleData | normalizeWebhookPayload | Says what happens |
flag | isRetryable | Booleans read as predicates |
Manager / Helper | InvoiceCalculator | Role, not dumping ground |
When a function both fetches from the network and formats a PDF and sends email, you cannot reuse or test any piece cleanly. Extract until each function has a sentence-length purpose: "parse the Stripe event", "apply the refund to the ledger", "enqueue the email".
If the code needs a comment to say i++ // increment i, the code is the problem. Reserve comments for non-obvious constraints: regulatory rules, race windows, "this looks redundant but removes a double-charge", links to tickets or RFCs.
// Charge idempotency: Stripe may deliver the same webhook twice.
// We key on event.id so a replay is a no-op, not a second refund.
await db.refunds.insert({ stripeEventId: event.id, amountCents: 499 })Suppose checkout applies a percentage discount, then tax:
function total(c: any, d: number, t: number) {
let x = 0
for (const i of c) x += i.p * i.q
x = x - x * d
return x + x * t
}
type MoneyCents = number
type LineItem = { unitPriceCents: MoneyCents; quantity: number }
function subtotalCents(items: LineItem[]): MoneyCents {
return items.reduce((sum, i) => sum + i.unitPriceCents * i.quantity, 0)
}
function applyPercentDiscount(amountCents: MoneyCents, rate: number): MoneyCents {
if (rate < 0 || rate > 1) throw new RangeError(`discountRate out of range: ${rate}`)
return Math.round(amountCents * (1 - rate))
}
function applyTax(amountCents: MoneyCents, taxRate: number): MoneyCents {
if (taxRate < 0) throw new RangeError(`taxRate must be >= 0: ${taxRate}`)
return Math.round(amountCents * (1 + taxRate))
}
const items = [{ unitPriceCents: 1000, quantity: 2 }]
const afterDiscount = applyPercentDiscount(subtotalCents(items), 0.1)
console.log(applyTax(afterDiscount, 0.08))Output:
1944$10 × 2 = 2000 cents, 10% off → 1800, then 8% tax → 1944. Named stages, explicit units, validated rates. A one-line product change — "discount applies after tax in EU, before tax in US" — no longer requires decoding d and t.
Common mistakes
- Names that hide units (
d,t,flag). - Comments that narrate the next line.
- A second style beside the house style because you would have designed it differently.
Check yourself
You see function handleData(d, flag). What should you reach for first?
Rename: units and a predicate. handleData and flag tell the next reader nothing.
When is a comment worth keeping?
When it explains a why — a race, a regulation, a double-charge trap — not the syntax of the next line.
Key takeaways
- Names and function boundaries carry most of the documentation.
- Comments explain why. If you need a comment to say what, rename the code.
- Match the repo's conventions; two styles cost more than one imperfect style.
- Named stages with units make the next product change cheap.
Further learning
Next, make failure part of the interface: error handling and illegal states.
Related
Writing code others can maintain
Managing complexity with deep modules, clear naming, honest comments, consistent style, and error strategies that shrink the blast radius of change.
Deep modules
A deep module hides a lot of complexity behind a small interface. Shallow pass-through layers add names without adding leverage.
Code review
Author small, reviewable PRs and give reviews that improve both the code and the author — design first, tone that teaches, turnaround that unblocks.