Verbs and names

Last updated: 2026-08-18A closed lexicon of 33 verbs with contracts, mandatory unit suffixes, boolean prefixes, and the words that say nothing.

Verbs and names

A function is an action, so its name starts with a verb. The verb comes from a closed lexicon, and every verb carries a contract that must not be broken.

Reading

find*May return null. Never throws — absence is a normal outcome
get*Guarantees a value. No value means it throws. No null in the type
list*Always a collection. An empty collection is neither an error nor null
count*A number. Never null

This one pair changes how code reads:

ts
const track = await findTrack(trackId)
if (track === null) return makeFail('music.track.missing', 'Track not found')

const owner = getOwner(track)

Without opening either function you can see that absence is routine for the first and a bug for the second. getOwner here means "an owner must exist, otherwise the data is broken".

Boundaries and storage

read* / write*Crossing an I/O boundary: network, disk, object storage
load* / save*A whole aggregate through storage (the database)

Separating read from load is not pedantry: the name tells you whether the call survives offline and whether it is worth caching.

Computation

make*A pure constructor of a value. No I/O, no clock, no randomness
derive*A pure computation from data you already hold
resolve*Picking one option out of several by rules
parse* format*Parsing and presentation: input → output, no state
encode* decode*Changing representation without changing meaning
normalize*Reducing to a canonical form
sign* verify* hash*Cryptographic transformations

Change

create* / delete*A domain mutation: changes state and emits an event
archive* / restore*Reversible removal from circulation
ensure*Idempotently brings state to the required shape — calling twice changes nothing
assert*Throws when an invariant is broken, otherwise void. Returns nothing
apply*Applies a prepared change to a target
set*Assigns a value; nothing else happens downstream

Signals and lifecycle

emit*To the bus. The recipient is unknown and irrelevant
send*To a specific recipient — the recipient is part of the name: sendVerifyEmail
start* / stop*The lifecycle of a process or subsystem
use* with* render*UI code only: hooks, wrappers, rendering

Banned verbs

Each one is banned because it states no action — it states that the author had not decided what the function does.

BannedUse instead
handle*Name the action: submitLogin, retryPayout, dropStaleSession
process*derive* / apply* / the precise transformation verb
manage*The function does more than one thing — split it
do* perform* execute* run*Name the action
check*is* if it returns an answer, assert* if it throws
init*start* for a process, make* for a value
update*save* / apply* / set* — say what actually happens
fetch* retrieve*read* (I/O) / load* (aggregate) / get*
calculate* compute*derive*
generate* build* prepare*make*
setup*ensure* / start*
validate*assert* (throws) / parse* (returns a parse)
transform* convert*derive* / format* / encode*
determine*resolve*
trigger*emit* / send*

Word order: domain → qualifier → unit

The usual way
urlOfCover
refreshTimeout
amount
GRAIN
trackCoverUrl
sessionRefreshTtlSec
payoutAmountCents

General on the left, specific on the right. Names like these sort alphabetically into meaningful groups, and autocomplete on the first word surfaces the whole domain at once.

A number without a unit is an error

This is the one GRAIN rule that catches an entire class of production bugs: seconds handed to something that expected milliseconds.

SuffixMeaning
*AtA point in time, always UTC
*Ms *Sec *Min *Hours *DaysA duration
*Bytes *Kb *Mb *GbA size
*CentsMoney — always minor units, always an integer
*RatioA fraction, 0..1
*PctA percentage, 0..100
*Count *IndexA count and a position
*Px *Deg *Hz *Bpm *DbDomain quantities

Dimensionless values that need no suffix: x y z width height depth port page limit offset version priority. The list is closed — if your quantity is not on it, it has a unit.

Booleans — seven prefixes, no others

is · has · can · should · was · will · must

ts
const isPublished = track.publishedAt !== null
const hasActiveSubscription = subscription.state === 'active'
const canPublish = isOwner || isAdmin

enabled, published, flag, status are not boolean names.

Words that say nothing

Banned as the last word of a name and as a whole name:

data info obj object temp tmp stuff misc thing things
res ret arr str num flag val result
helper handler manager wrapper util utils

userDatauser. trackInfotrack or trackSummary. parseResultparsedTrack. If removing the empty word makes the name indistinguishable from another one in the same scope, then either those two things really are the same thing, or one of them needs a real name.

Abbreviations

Exactly these are allowed: id url uri db io rpc ttl utc api sql css html json jwt s3 ip dns cdn ui ms.

Everything else is spelled out: config not cfg, request not req, message not msg, error not err, index not idx, previous not prev.

Name length is proportional to scope. In a two-line callback, t is honest and fine. In an exported API there are no single-letter names.

Self-praise

enhanced advanced comprehensive ultimate robust powerful seamless smart intelligent optimized improved modern simple easy quick — banned in names. That is a judgement, not a description; it expires the moment it is committed and almost always means "I could not find what makes this version different from the last one".

Verbs and names | AS Docs