Case file — S5

Inline-First

Don't introduce names for nothing.

A binding earns its keep when its name carries information the expression does not. Otherwise it is noise: a hop the reader has to make for nothing.

ByAdam LewisPublished3 May 2026Reading12 minVersionv1.0ConfidenceHigh
§0b

Opinion

Many of the bindings I see in review do not earn their existence. const userName = user.name; followed by a single use of userName on the next line. The name carries nothing user.name does not. Fowler frames the move directly: Inline Variable is the inverse of Extract Variable.1Martin FowlerRefactoring, 2nd ed. (Addison-Wesley, 2018), ch. 6 “A First Set of Refactorings”. Extract Variable and Inline Variable as inverses; one continuum, two directions. Extract when the expression hides intent; inline when the binding adds nothing the expression did not already say.

The other side of the same continuum is the magic-number case. A function that retries three times and waits 5000 milliseconds between attempts is harder to read than the same function with MAX_RETRIES = 3 and BACKOFF_MS = 5000. The literal carries meaning the expression cannot, and the meaning is exactly what the constant's name records.2Steve McConnellCode Complete, 2nd ed. (Microsoft Press, 2004), §12.7 “Named Constants”. The literal-with-meaning side of the rule. Named constants pay for themselves; magic numbers do not. Same principle, opposite direction: extract when the literal hides intent; inline when the binding hides nothing.

The third case is the immediate-return alias. const result = compute(); return result; is two lines doing the work of one. The alias does not narrow the type, name a stage, or carry intent; it just gives the reader an extra hop. SonarJS catches this directly with prefer-immediate-return; the rule is in the Prickles repo's lint config and it has paid for itself many times over.3SonarSourceESLint plugin SonarJS — `prefer-immediate-return` rule. Catches `const x = compute(); return x;` patterns directly; the linter form of the immediate-return side of the continuum. Pair the rule with F2 Intention-Revealing Names: when the name carries meaning, keep it; when it does not, drop it.

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.

A binding earns its keep when its name carries information the expression does not. Inline first; extract on demand. Three settled cases: literals with meaning earn a const NAME; single-use trivial expressions stay inline; values computed and immediately returned drop the alias.

/tenet/inline-first/S5
§0c

AI eyes only

Rule: do not introduce names for nothing. A binding earns its keep when its name carries information the expression does not.

Reject: const userName = user.name; followed by a single use of userName. Reject: extracting a one-line helper that has one caller and a name no clearer than the body. Reject: aliases that just restate the right-hand side.

Generate: inline single-use trivial expressions. Bind only when the name documents intent (e.g. const isAdmin = user.role === "admin") or the expression is reused.

Diagnostic: read the binding aloud. If the LHS only restates the RHS, drop the binding.

§0d

Why?

  • Reduces the number of hops the reader has to make. A function whose bindings all earn their existence reads top-to-bottom; a function whose bindings are noise reads as a sequence of chained dictionary lookups.
  • Functions get shorter without losing meaning. Pairs cleanly with S2 Cyclomatic Caps — a function that fails the line cap often passes once the noise bindings are inlined.
  • The literal-with-meaning case earns a named constant; the function reads as MAX_RETRIES rather than 3, and the constant moves to the top of the module where everyone can find it.
  • Coding agents stop generating noise bindings when the lint catches them. The model inherits the rule for free; output sharpens inside a week of wiring prefer-immediate-return into the lint.
  • Cuts review cost. Reviewers stop arguing about whether userName earns its existence; the lint flags it and the conversation is “does the name carry information?” instead of “is this binding necessary?”
  • Hands the naming conversation to F2 Intention-Revealing Names. The binding earns its keep when the name does — F2 picks the names that earn keep; Inline-First removes the ones that do not.
  • Improves diff quality. Renaming a noise binding is two lines; inlining and re-extracting with a real name is the same diff with the meaning of the rename made obvious. Reviewers catch more in the second form than the first.
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

Origins

Martin Fowler catalogued Extract Variable and Inline Variable as inverses in the second edition of Refactoring.1Martin FowlerRefactoring, 2nd ed. (Addison-Wesley, 2018), ch. 6 “A First Set of Refactorings”. Extract Variable and Inline Variable as inverses; one continuum, two directions. Ch. 6 “A First Set of Refactorings” pairs the two moves directly: Extract Variable when an expression is hard to follow and a name would clarify it; Inline Variable when a binding is in the way of further refactoring or when it does not carry information the expression does not. The two moves are one continuum; engineers who read the chapter come away with one rule, not two.

The magic-number side of the same rule was named long before Refactoring. Brian Kernighan and P. J. Plauger's The Elements of Programming Style (1974) gave it as Rule 19: “A name should be longer the larger its scope” — and the inverse: a literal with project-wide meaning earns a project-wide name, but a literal with one-line meaning stays inline.5Brian W. Kernighan & P. J. PlaugerThe Elements of Programming Style (McGraw-Hill, 1974; 2nd ed. 1978). Rule 19: “A name should be longer the larger its scope.” The earliest published statement of the magic-number / named-constant continuum. McConnell's Code Complete §12.7 carries the same rule under the heading “Named Constants”.2Steve McConnellCode Complete, 2nd ed. (Microsoft Press, 2004), §12.7 “Named Constants”. The literal-with-meaning side of the rule. Named constants pay for themselves; magic numbers do not. The rule is older than most working engineers; the editorial decision is which side of the line a given literal falls on.

The immediate-return side of the rule lives in the SonarJS rule sonarjs/prefer-immediate-return.3SonarSourceESLint plugin SonarJS — `prefer-immediate-return` rule. Catches `const x = compute(); return x;` patterns directly; the linter form of the immediate-return side of the continuum. The rule catches the const result = compute(); return result; pattern directly. SonarSource's rationale is exactly this tenet's — the alias carries no information and adds a hop. Same continuum; different surface.

The principle the three sides share is older still. Edsger Dijkstra's “The Humble Programmer” (1972 ACM Turing Award lecture) named the underlying instinct: a programmer's job is to manage the cognitive complexity the code presents to the reader. Every binding is a hop; every hop has a cost; every hop has to earn its existence by offering meaning the reader did not have. Inline-First is the operational form of that instinct.

§2

Quotes

Variables provide names for expressions, and as such they are usually a Good Thing. But sometimes the name doesn't really communicate more than the expression itself. At other times, you may find that a variable gets in the way of refactoring its containing code. In these cases, it's a good idea to inline the variable.

Martin Fowler · Refactoring catalogue — Inline Variable

Expressions can become very complex and hard to read. In such situations, local variables may help break the expression down into something more manageable. The name should communicate the purpose of the expression.

Martin Fowler · Refactoring catalogue — Extract Variable

The use of magic numbers is a common source of errors. Hide them in named constants. Use the names to clarify intent and to ensure that a single change updates every use of the value.

Steve McConnell · Code Complete 2e (2004), §12.7

A name should be longer the larger its scope.

Brian W. Kernighan & P. J. Plauger · The Elements of Programming Style (1974)
§3

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.

  1. 01
    Martin Fowler · 2018
    The canonical statement of the continuum. Two moves, one rule: extract when a name would clarify; inline when the binding adds nothing the expression does not.
  2. 02
    Martin Fowler · 2018
    The operational form of the inline-first side. Replace the binding with the expression at every use; remove the declaration; run the tests.
  3. 03
    Martin Fowler · 2018
    The inverse move. Used when an expression is hard to read and a name would clarify intent. The two halves of one rule.
  4. 04
    Steve McConnell · 2004
    The literal-with-meaning side. Named constants pay for themselves; magic numbers do not. The canonical reference for the rule.
  5. 05
    Brian W. Kernighan & P. J. Plauger · 1974, 1978
    “A name should be longer the larger its scope.” The continuum named in 1974; the half-century-old precursor of the lint config.

Sixteen sources, two stances. The supporters cluster on the continuum itself: Fowler's Extract Variable / Inline Variable pair, McConnell's named-constants chapter, and Kernighan & Plauger's Rule 19. The qualifiers further down carry the “sometimes the binding is the documentation” reading. There is no substantive opposition; the disagreement is about which side of the continuum a given binding falls on.

§4

Examples

Viewing: TypeScript.
Avoid
Fileformat-invoice.ts
// Before: a single-use alias that names nothing. sonarjs/prefer-immediate-return fires.function formatTaxableHedgehogInvoice(invoice: HedgehogInvoice): string {  const taxableAmount = computeBaseTax(invoice);  return formatAmount(taxableAmount);}
Prefer
Fileformat-invoice.ts
// After: the binding is gone. The expression returns directly.function formatTaxableHedgehogInvoice(invoice: HedgehogInvoice): string {  return formatAmount(computeBaseTax(invoice));}
§4b

Enforcement

Viewing: TypeScript.

Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.

RuleToolCatches
no-magic-numbersESLint coreliterals used directly in code where a named constant would carry intent. Configurable allowlist for trivial values; `enforceConst: true` requires the new binding to be a `const`.
sonarjs/prefer-immediate-returneslint-plugin-sonarjsthe `const x = compute(); return x;` pattern. Auto-fixable; the alias drops, the function reads as the expression.
sonarjs/no-redundant-booleaneslint-plugin-sonarjsboolean literals used where the expression already evaluates to the same boolean. The shape that hides behind unnecessary intermediate bindings.
sonarjs/no-gratuitous-expressionseslint-plugin-sonarjsexpressions whose value is already known at compile time — usually the symptom of a redundant intermediate binding that should be inlined.
eslint.config.mjsconfiguration snippet
import tseslint from 'typescript-eslint';
import sonarjs from 'eslint-plugin-sonarjs';

export default tseslint.config({
  files: ['**/*.{ts,tsx}'],
  plugins: { sonarjs },
  rules: {
    'no-magic-numbers': ['warn', {
      ignore: [-1, 0, 1, 2],
      ignoreArrayIndexes: true,
      enforceConst: true,
      detectObjects: false,
    }],
    'sonarjs/prefer-immediate-return': 'error',
    'sonarjs/no-redundant-boolean': 'error',
    'sonarjs/no-gratuitous-expressions': 'error',
  }
});
§4c

AI rules

File.cursor/rules/s5-inline-first.mdc
---
description: Prickles S5 — Inline-First
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---

## Prickles S5 — Inline-First

Inline first; extract on demand. A binding earns its keep when its name carries information the expression does not.

Three settled cases. Literals with meaning earn a `const NAME` so the meaning is recorded once and cited many times. Single-use trivial expressions stay inline. Values computed and immediately returned drop the alias.

When the lint flags an immediate-return alias or a magic number, do the rewrite. The rule is the conversation, not the verdict.

Refuse to introduce a name that does not narrow a type, name a stage, or carry intent. The default is the expression; the name has to earn its existence.

Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.

§5

Counter-argument

Counter

The strongest steelman is the “binding-as-documentation” school's.4Kent BeckSmalltalk Best Practice Patterns (Prentice Hall, 1996). Beck argues that intermediate bindings are sometimes the cheapest way to document the meaning of an expression — the binding-as-documentation reading. The reply: pair Inline-First with F2. Kent Beck and Steve McConnell both argue that intermediate bindings are sometimes the cheapest way to document the meaning of an expression: const taxableAmount = grossAmount - exemption; reads better than the inline arithmetic even when taxableAmount is used once. The reply is that the canon agrees; the rule is “extract when the name carries information.” The disagreement is about whether taxableAmount carries information that grossAmount - exemption does not, and that judgement is exactly what the code-review conversation is for. The lint surfaces the candidate; the reviewer decides.

§6

Counter-argument retort

Reply

Beck and McConnell are right that the binding is sometimes the documentation, and the canon does not dispute that.4Kent BeckSmalltalk Best Practice Patterns (Prentice Hall, 1996). Beck argues that intermediate bindings are sometimes the cheapest way to document the meaning of an expression — the binding-as-documentation reading. The reply: pair Inline-First with F2. The rule is not “always inline”; it is “inline first, extract on demand.” The default direction matters because the cost is asymmetric: noise bindings accumulate quietly, and an extra five lines of unnecessary aliases is the reason the function fails the cap on S2 Cyclomatic Caps. The lint surfaces the candidate; the reviewer decides whether the binding carries meaning the expression does not.

The honest residue is the type-narrowing case. In TypeScript and Python, a binding sometimes narrows a union or strips an optional, and the binding is load-bearing for the type checker even when its name adds nothing. The reply is that the type narrowing is the meaning the binding carries — the linter that prefers immediate return knows about this and respects type-narrowing bindings; the rule is not breaking the type system.

The genuine residue is the “intermediate name as comment” case Beck names. The canon's answer: pair Inline-First with F2 Intention-Revealing Names. The binding stays when its name is doing real work — taxableAmount over grossAmount - exemption passes the test; userName over user.name does not. The lint surfaces both; the reviewer decides which side of the continuum each binding falls on. The rule is the conversation, not the verdict.

The principle is one continuum: extract when the name carries information; inline when it does not. Three sub-rules, one rule. The lint is the trigger; the review is the conversation; the result is the function whose bindings all earn their keep.

§7

Notes

  1. [1]Martin FowlerRefactoring, 2nd ed. (Addison-Wesley, 2018), ch. 6 “A First Set of Refactorings”. Extract Variable and Inline Variable as inverses; one continuum, two directions.
  2. [2]Steve McConnellCode Complete, 2nd ed. (Microsoft Press, 2004), §12.7 “Named Constants”. The literal-with-meaning side of the rule. Named constants pay for themselves; magic numbers do not.
  3. [3]SonarSourceESLint plugin SonarJS — `prefer-immediate-return` rule. Catches `const x = compute(); return x;` patterns directly; the linter form of the immediate-return side of the continuum.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-04-27