Intention-Revealing Names
The signature is the spec.
A reader who sees only the function name and its parameters should know what the call does. If the name does not carry that load, the code has not finished saying it yet. Rename, split per F1 Single Responsibility, or introduce a type per T1 Domain-Driven Types.
Opinion
Read the call, not the body. The name and the parameters carry the work, or the work is not finished. Most teams agree the name matters; the disagreement is the choice of name, and the answer is rarely clever. The right name is the word the product owner uses, written in a type that says the same thing.
Naming makes F5 Self-Documenting Code possible. Naming gives F1 Single Responsibility its evidence: a function that resists a name is doing two things, and the name is the diagnostic that catches it before the test does. Naming carries T1 Domain-Driven Types from the type system into the prose: a parameter named id: string admits anything the compiler counts as a string; rename it userId: UserId and the call site, the signature, and the type all say the same thing.
Generated code makes the bar harder to hold. Models abbreviate by habit; reviewers approve proc because the prompt was about processing. Six months later someone reads proc(o) and pings the channel. Block the merge on the vocabulary, not the abbreviation. If the product owner cannot read the call site without a glossary, the name has not finished.
Copy a note and link
Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.
Function name and parameters should tell the reader what the call does. If they don't, rename until they do. /tenet/intention-revealing-names/F2
AI eyes only
Rule: the signature is the spec. A reader of name + parameters knows what the call does.
Reject: handle, process, do, data, info, obj, tmp, single-letter identifiers (except loop counters in trivial scope), and abbreviations not in the project glossary.
Generate: verb-led function names that name the outcome, not the mechanism. Parameter names that name the role, not the type. Branded primitive types (UserId, EmailAddress) over string.
Diagnostic: state the function in one sentence using only the name and parameter names. If that sentence is wrong or empty, rename before continuing.
Why?
- The call site is self-contained: no comment, no wiki link, no tribal memory.
- Honest names make single-purpose units visible; vague names smuggle multi-job functions past review.
- Feeds self-documenting code — when names work, explanatory comments rot less.
- Pairs with domain-driven types:
OrderIdbeatsstring; the name and the type say the same thing. - Test titles become assertions in prose — the failure message reads like a specification, not a stack trace riddle.
- Distinct nouns improve search, cross-repo navigation, and code-intel recall.
- Reviewers validate behaviour against a promise they can see in the header; less working memory, fewer subtle approvals.
Origins
Robert C. Martin's Clean Code (2008) opens the case in chapter two: meaningful names, pronounceable identifiers, avoiding encodings. The craft advice still anchors most onboarding reading lists.1Clean Code (Prentice Hall, 2008), ch. 2 — meaningful, intention-revealing names as the first layer of expression.
Martin Fowler catalogued the opposite smell in Refactoring (2nd edition, 2018): “Mysterious Name” flags an identifier where something important is insufficiently clear, with renaming as the first move before any comment is added.2Refactoring, 2nd ed. (2018). Mysterious Name is smell #1 online; rename operations are the prescribed first move.
Joshua Bloch's Effective Java (3rd edition, 2017) builds the interfaces-and-types half of the story: APIs read as short sentences in the problem domain when the names are right.3Effective Java, 3rd ed. (2018). Item 68 and surrounding items tie package/type/member naming to readable APIs. Steve McConnell treats identifiers as part of documentation engineering in Code Complete (2nd edition, 2004): identifiers are the documentation that survives when the wiki does not.4Code Complete, 2nd ed. (Microsoft Press, 2004). Variables and routine names as active documentation.
John Ousterhout's A Philosophy of Software Design (2018) warns against cleverness that optimises for keystrokes over depth: obscure names leak complexity outward, and clarity beats puns unless the pun is shared vocabulary.5A Philosophy of Software Design (2018). Strategic naming vs tactical shortcuts; complexity must not leak through vague handles.
Quotes
The name of a variable, function, or class should answer all the big questions. It should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent.
Rename Symbol / Change Function Declaration — the smell is obscurity; the first refactor is almost always the name.
Use meaningful, pronounceable, searchable names. Avoid cryptic abbreviations and single-letter names, except as local variables whose scope is very small.
The best names are ones that reinforce understanding at the point of use and do not need to be deciphered.
Evidence
Twenty external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.
- 01The canonical craft primer: intention-revealing names, pronounceability, avoiding mental mapping.
- 02Names are the first refactor when behaviour is unclear at the callsite.
- 03Shared vocabulary collapses naming debates; one word per concept across code and speech.
- 04Code idioms should track domain speech — ties identifiers to product language explicitly.
- 05Treats naming as a documentation strategy; ties identifier quality to comprehension speed.
Sixteen sources across supports and a few qualifiers. The opposer row is mostly about how far to take Uncle Bob's bundle, not about skipping honest names. Where DDD and Pragmatic Programmer agree, the vocabulary is product-owned before it is keyboard-owned.
Examples
// Before: names hide what is being compared to what.function check(u: { subEnd: Date }, d: Date): boolean { return u.subEnd.getTime() < d.getTime();}
function isSubscriptionExpired(user: { subscriptionEndsAt: Date }, asOf: Date): boolean { return user.subscriptionEndsAt.getTime() < asOf.getTime();}
Enforcement
Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.
| Rule | Tool | Catches |
|---|---|---|
| @typescript-eslint/naming-convention | typescript-eslint | parameters, locals, functions and types that don’t match the agreed camel / Pascal split. |
| unicorn/prevent-abbreviations | eslint-plugin-unicorn | err, evt, btn, doc, str, num, strBuf — the abbreviations that hide the reader’s question. |
| id-length | ESLint core | single-letter locals outside your explicit exceptions list. |
eslint.config.mjsconfiguration snippet
import tseslint from 'typescript-eslint';
import unicorn from 'eslint-plugin-unicorn';
export default tseslint.config({
files: ['**/*.{ts,tsx}'],
plugins: { unicorn },
rules: {
'id-length': ['error', { min: 2, max: 48, exceptions: ['_', 'id', 'x', 'y', 'to'] }],
'unicorn/prevent-abbreviations': ['error', {
allowList: { Param: true, Ref: true, Props: true },
checkShorthandProperties: true,
}],
'@typescript-eslint/naming-convention': [
'error',
{ selector: 'variable', format: ['strictCamelCase', 'UPPER_CASE'], leadingUnderscore: 'allow' },
{ selector: 'function', format: ['strictCamelCase'] },
{ selector: 'typeLike', format: ['StrictPascalCase'] },
{ selector: 'parameter', format: ['strictCamelCase'], leadingUnderscore: 'allow' },
],
}
});AI rules
.cursor/rules/f2-intention-revealing-names.mdc---
description: Prickles F2 — Intention-Revealing Names
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---
## Prickles F2 — Intention-Revealing Names
Use names that answer why this exists and what it does in the domain vocabulary. If the signature does not carry the meaning, rename.
Functions state verbs in plain language; values and parameters are nouns or noun phrases. Booleans read as predicates (isExpired, hasBalance).
Refuse placeholders at review: tmp, data, info, handle, process, manager, helper — unless the PR body defines the missing noun they replace.
Match local convention (camelCase, snake_case, PascalCase) but never hide meaning behind brevity a newcomer cannot pronounce.
When splitting under F1, each new unit must earn a name that fits the story the callsite tells.Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.
Counter-argument
The steelman is keyboard fatigue and long identifiers in narrow columns: eight well chosen English words can wrap worse than eight clever abbreviations. Dense maths, physics, and cryptographic code also import symbols that look terse because the paper did. In those domains the glossary lives beside the code and abbreviations are community contract, not laziness.
Counter-argument retort
Width is an editor problem; reader confusion is a product problem. Wrap rules, panes and column limits adjust. When an identifier is long because the concept is long, the prompt is to introduce a domain type or extract a function with a shorter honest name, not to revert to mgr.
For notation-heavy kernels, the abbreviation is itself intention-revealing inside that community. Ship the paper citation or standard section ID next to the module: the name stays honest for its declared audience.
Default stance for application code stays: pronounceable domain words, predicates on booleans, verbs on operations, nouns on data. Everything else is an exception that gets defended in the PR description.
Notes
- [1]Robert C. Martin — Clean Code (Prentice Hall, 2008), ch. 2 — meaningful, intention-revealing names as the first layer of expression.
- [2]Martin Fowler — Refactoring, 2nd ed. (2018). Mysterious Name is smell #1 online; rename operations are the prescribed first move.
- [3]Joshua Bloch — Effective Java, 3rd ed. (2018). Item 68 and surrounding items tie package/type/member naming to readable APIs.