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
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
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_ATTEMPTSfinds every use;4finds noise.
Steps
- Pick a literal and decide its meaning. Name the meaning, not the value (
MAX_ATTEMPTS, notFOUR). - Declare the constant in the narrowest scope that covers all its uses: function, module, or a shared config module.
- Find every occurrence of the literal. Replace only those with the same meaning. Run the tests.
- 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:
0as a starting index,1inn + 1,100in a percentage formula,2in "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.
Related
- Fixes Primitive Obsession, Duplicate Code and Comments that explain numbers.
- Extract Variable is the local, computed equivalent.
- Replace Type Code with Class handles sets of related codes.
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
- Primitive ObsessionDomain concepts like money, emails, IDs or statuses are passed around as raw strings and numbers, so their rules get re-checked everywhere.
- Duplicate CodeThe same logic appears in two or more places, so every fix and change must be made several times and the copies drift apart.
- CommentsComments that explain what unclear code does are a sign the code should be renamed or restructured so it explains itself.
- Shotgun SurgeryOne logical change forces small edits in many classes, so one missed edit ships an inconsistent system.
See also
- Extract VariableName a hard-to-read expression or sub-expression by assigning it to a well-named local variable.
- Replace Type Code with ClassReplace a set of string or number codes that don't affect behavior with a dedicated type, so only valid codes can be used.
- Replace Data Value with ObjectTurn a primitive field that has grown rules and companions into a small class that owns its data and behavior.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.