Skip to content
aipatterns.dev

Refactoring techniqueOrganizing Data

Replace Magic Number with Symbolic Constant

Give a literal number or string with special meaning a named constant, and use the name everywhere the value appears.

Also known as
Replace Magic Literal

Problem

Literals such as 86400, 0.2, 429 or "PENDING_REVIEW" appear in the code with no explanation. Readers guess their meaning, and when a value changes you must find every copy, telling apart the copies of this 30 from unrelated 30s.

Solution

Declare a constant with a name that states the value's meaning, and replace each occurrence that has that meaning with the name.

Before

TypeScript
async function fetchWithRetry(url: string): Promise<Response> {
  for (let attempt = 0; attempt < 4; attempt++) {
    const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
    if (res.status !== 429 && res.status < 500) return res;
    await sleep(Math.min(250 * 2 ** attempt, 5000));
  }
  throw new Error(`gave up on ${url} after 4 attempts`);
}

After

TypeScript
const MAX_ATTEMPTS = 4;
const REQUEST_TIMEOUT_MS = 8_000;
const BASE_BACKOFF_MS = 250;
const MAX_BACKOFF_MS = 5_000;
const HTTP_TOO_MANY_REQUESTS = 429;
const HTTP_SERVER_ERROR_MIN = 500;

async function fetchWithRetry(url: string): Promise<Response> {
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
    const res = await fetch(url, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
    const retryable = res.status === HTTP_TOO_MANY_REQUESTS || res.status >= HTTP_SERVER_ERROR_MIN;
    if (!retryable) return res;
    await sleep(Math.min(BASE_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS));
  }
  throw new Error(`gave up on ${url} after ${MAX_ATTEMPTS} attempts`);
}

Why

  • Each number explains itself, and the retry policy reads as a policy.
  • Changing the attempt count updates the loop and the error message together; the two can no longer drift.
  • Constants are searchable. MAX_ATTEMPTS finds every use; 4 finds noise.

Steps

  1. Pick a literal and decide its meaning. Name the meaning, not the value (MAX_ATTEMPTS, not FOUR).
  2. Declare the constant in the narrowest scope that covers all its uses: function, module, or a shared config module.
  3. Find every occurrence of the literal. Replace only those with the same meaning. Run the tests.
  4. If a set of related literals form an enumeration (statuses, roles), consider an enum, a union type, or Replace Type Code with Class instead.

Watch out

  • Don't name obvious values: 0 as a starting index, 1 in n + 1, 100 in a percentage formula, 2 in "halve this".
  • Two equal literals with different meanings must get two constants. Merging them couples unrelated rules.
  • If a value differs between environments or tenants, it is configuration, not a constant. Load it from config.
  • If the literal needs behavior (validation, formatting, units), it wants a type: Replace Data Value with Object.
  • Inverse: inlining a constant back is rarely useful, except for a constant used once whose name repeats the value.

Agent checklist

  • Each constant name describes meaning, not value.
  • Only occurrences with the same meaning were replaced.
  • Constants live in the narrowest sensible scope.
  • Environment-dependent values were moved to configuration instead.
  • Tests pass.

Treats these smells

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.