Guide

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

WeakStrongerWhy
ddelayMsUnits belong in the name
handleDatanormalizeWebhookPayloadSays what happens
flagisRetryableBooleans read as predicates
Manager / HelperInvoiceCalculatorRole, 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.

TypeScript
// 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:

TypeScript
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.