Overview
This page is the language-agnostic baseline. Every per-language page in coding/ assumes these rules already hold. When a language page contradicts something here, the language page wins for that language only. Start here, then read python, typescript, swift, pine-script, or shell for the specifics.
Name things with specific nouns
Use names that say what the value is, not what category it belongs to.
activeUserIds, notdata,items, orlist.retryBudgetMs, nottimeoutordelay.parseInvoiceXml, notprocessorhandle.
No abbreviations the reader has to decode. usr, cfg, mgr, svc save four characters and cost a lookup every time someone new reads the code. The exceptions are conventional acronyms the field already uses: id, url, http, db.
A good name kills its own comment. If you wrote // the list of currently active user IDs above const data, rename data and delete the comment.
Comment the why, never the what
Code shows what it does. Comments exist for context the code cannot carry.
- Why this constant is 7 and not 10 (link to the incident or RFC).
- Why this branch exists (the upstream API returns
nullon Wednesdays). - Why this looks slower than the obvious version (the obvious version allocates on a hot path).
Delete every comment that paraphrases the next line. // increment counter above counter += 1 is noise. If a block needs a // what comment to be readable, the block is wrong; extract a named function instead.
Handle errors at boundaries
Errors travel up to a boundary, get logged with context, and turn into a response the caller can act on. Internal code raises; boundary code catches.
- A request handler, a queue worker, a CLI entry point, a UI event handler: these are boundaries. Catch here.
- A parser, a calculator, a domain function: these are internals. Raise here.
Avoid try/catch in the middle of business logic. A try block three levels deep that swallows the exception and returns null is the source of every silent production bug. Either the caller knows how to recover, or the error goes up.
Audit dependencies before adding them
Every dependency is a long-term cost. Before npm install or uv add:
- Read the package’s source. If it is under 200 lines, copy the function you need into your repo and delete the dependency.
- Check the last commit date, the open issue count, and the maintainer’s bus factor.
- Check the install size and the transitive dependency tree. A 2 MB package that pulls 47 sub-packages is a supply-chain liability.
Pin exact versions in production code. Lockfiles must be committed. Renovate or Dependabot updates land in their own PRs with the changelog summarized.
Test the golden path and the boundary cases
A test suite that only covers the happy path covers nothing. For every function worth testing, write:
- The golden path: typical input, expected output.
- The empty case: empty string, empty list, zero,
None/null/nil. - The boundary: max length, off-by-one, the day of the month rollover.
- The error case: bad input, missing field, the upstream timing out.
Coverage percentage is a vanity metric. Coverage of the four cases above is the real bar.
Prefer deletion over abstraction
Two callers of similar code do not need a shared abstraction. Three callers sometimes do. Five callers usually do. Premature abstraction is the larger sin, because it locks in the wrong shape before the real shape is known.
When in doubt, duplicate. Wait for the third use, then extract the common shape that the three uses actually share, not the shape you imagined they would.
Resist premature optimization
Write the obvious version first. Profile under realistic load. Optimize the one hot path the profile points at, and leave the rest alone.
Micro-optimizations (a clever loop, a hand-rolled cache, an O(n) to O(log n) swap on a list of 12) cost readability and ship bugs. Measure before you change, and measure after to confirm the change helped.