>_devkit
catalogue
Stablev1.1.0

naming-conventions

by @chempa

Use when naming anything - a project, repo, branch, class, function, table, column, field, endpoint, env var, container, or commit. The house standard, so every project reads the same.

Naming

Code should be self documenting. A name is the only documentation that cannot go stale, because it is read every time the thing is used. Comments drift, wikis rot, but a badly named field is wrong in every file that touches it.

This is the house standard. The point is not that these choices are uniquely correct; it is that one consistent choice beats a better inconsistent one. Every project follows this, including the ones already written that do not.

Why bother: what drift actually looks like

From a live codebase, one index file, two fields:

lastSeen      lastSeenAt

Same concept, two names, in the same file. Now every consumer has to know which collection uses which, no autocomplete helps, and any refactor has to check both. That cost never goes away, and it compounds with every new field a developer invents by guessing.

Across repos it shows up as: component files in PascalCase in one project and kebab-case in another, snake_case collections next to camelCase fields, and three different repo naming patterns for the same product.

None of these are bugs. Together they are why a new project takes a week to feel familiar instead of an hour.

The principles

Seven rules. Everything in the reference files follows from them.

1. Name the thing, not its implementation. portsidemotors-fast-api bakes a framework into a repo name that will outlive the framework. portsidemotors-api says what it is. Same for UserArrayList, mongo_client_wrapper, redis_cache.

2. Length scales with scope. i inside a three-line loop is fine. A module-level export, a database column, or a public endpoint is read by people with no context, so spell it out. The rule is proportional: the further a name travels, the more it must carry.

3. Units and currency live in the name. priceMinor, timeoutMs, sizeBytes, distanceKm. This is the single highest-value convention here, because the alternative is a bug that silently produces a number 100 times too large. price is a question; priceMinor is an answer.

4. Booleans read as assertions. isActive, hasBalance, canRefund, shouldRetry. Never negate: notReady produces if (!notReady), which nobody parses correctly at speed.

5. One concept, one word. Pick get or fetch or retrieve and never mix them in a codebase. Two words for one idea forces a lookup every time. Symmetric operations use symmetric pairs: create/delete, add/remove, start/stop, open/close. Do not pair create with destroy.

6. Ban filler nouns. Manager, Helper, Util, Data, Info, Processor, Handler, Service when it means nothing. A class named AuctionManager is an admission that you have not decided what it does. Name the responsibility: AuctionSettler, BidValidator, PriceCalculator.

7. No abbreviations outside the approved list. usr, calc, tmp, acct save four characters and cost a lookup. Approved everywhere: id, url, uri, api, db, http, io, ui, min, max, ms. Nothing else without adding it here.

The master table

ArtefactCaseShapeExample
Repo / projectkebab<product>-<role>portsidemotors-api
Directorykebabplural for collections of thingsbank-accounts/
Git branchlowerenvironment namedev, beta, prod
Commitconventionaltype(scope): imperativefeat(orders): add refunds
Python filesnakebank_accounts.py
Python func / varsnakeverb phrase for functionssettle_auction
Python classPascalnoun phraseAuctionSettler
Python constantSCREAMINGMAX_BID_MINOR
TS / JS filekebabskill-builder.ts
React component filePascalmatches the exportResourceTable.tsx
TS type / interfacePascalno I prefixSkill, BidResult
TS func / varcamelbuildInstallScript
CSS custom propertykebab--<category>-<name>-<variant>--color-ink-primary
SQL tablesnakepluralbank_accounts
SQL columnsnakecreated_at, price_minor
Mongo collectionsnakepluralproxy_bids
Mongo fieldcamelmatches the JSON on the wirecreatedAt, priceMinor
Foreign keymatches host<entity>Id / <entity>_idauctionId, auction_id
Timestampmatches hostalways <verb>AtcreatedAt, lastSeenAt
HTTP endpointkebabplural nouns, no verbs/v1/bank-accounts
Env varSCREAMING<AREA>_<THING>MONGODB_URL
Docker servicekebab<product>-<role>portsidemotors-api
Containerkebab<product>-<role>-<env>portsidemotors-api-dev
Image taglowerbranch, plus short sha:dev, :a1b2c3d
CI jobsnakeverbbuild_image, deploy

The two casing rules that look inconsistent are deliberate, and the reasoning is in references/data.md: a collection is a namespace (like a file), a field is serialized straight into JSON that a browser consumes.

Deeper references

files, modules, and the naming smells that signal a design problem

fields, keys, enums, timestamps, money, and schema evolution

services, containers, environments, endpoints, env vars, commits, tags

Applying this to code that already exists

Do not open a rename pull request across a live codebase. It produces a diff nobody can review, and it breaks every open branch.

Instead:

  1. New code follows the standard, from today, with no exceptions.
  2. Renames ride along with work you were already doing in that file.
  3. Data field renames are migrations, not edits: write both, backfill, read

new, drop old. See the migration section in mongodb-production.

  1. Fix the ambiguous pairs first. lastSeen versus lastSeenAt is worth a

dedicated change because it actively misleads. A merely unfashionable name is not.

The goal is convergence, not a big bang.

Maintainability

Naming is the cheapest maintainability lever there is, which is why it is worth being rigid about something that looks like taste.

Make it mechanical. A convention enforced by review is true for the files written this month. Most of the master table is expressible as a lint rule, and a rule the machine checks is one nobody has to remember on a Friday:

ruff:    select = ["N"]                          # pep8-naming
eslint:  @typescript-eslint/naming-convention     # per-selector rules

Add a filename check for the two cases those miss (PascalCase.tsx for components, kebab-case.ts for everything else) and the standard stops depending on anyone having read this document.

Some names are one-way doors. A local variable is free to rename. A name that has reached a database column, a collection field, a URL, an env var, or a client's integration cannot be changed by editing code, because something outside your repo has already copied it. Those get thought about once, properly, before they ship. This is the whole reason the units rule matters most: getting priceMinor wrong is not a rename, it is a migration plus a conversation with everyone consuming it.

Consistency is now an input, not a courtesy. A model writing code in your repo infers the convention from the code it can see. One consistent pattern produces more of that pattern. Four competing patterns produce a fifth. That makes convergence worth doing for its own sake, and it makes the ambiguous pairs (lastSeen next to lastSeenAt) more expensive than they used to be, because they are now teaching material.

See the maintainability skill for the enforcement ladder and for which decisions deserve the deliberation.

Copy: no em-dashes

Never use an em-dash (U+2014, the long dash) in identifiers, commit messages, API descriptions, or documentation. It is the loudest tell that text was generated. A full stop or a colon is almost always the better edit.