Refactoring techniqueComposing Methods
Remove Assignments to Parameters
Stop reassigning a function parameter inside its body; copy it into a local variable and work on that instead.
Problem
A function changes the value of one of its parameters partway through. Below that line the name no longer means what the caller passed in. Readers misread it, and in languages with pass-by-reference parameters (C#'s ref, C++ references, PHP's &) the caller sees the change too.
Solution
Treat parameters as read-only. Introduce a local variable for the changing value and leave the parameter untouched.
Before
function nextRetryDelay(attempt: number, baseMs: number, maxMs: number): number {
if (attempt > 10) attempt = 10;
baseMs = baseMs * 2 ** attempt;
if (baseMs > maxMs) baseMs = maxMs;
baseMs = baseMs + Math.floor(Math.random() * 250);
return baseMs;
}After
function nextRetryDelay(attempt: number, baseMs: number, maxMs: number): number {
const cappedAttempt = Math.min(attempt, 10);
const exponential = baseMs * 2 ** cappedAttempt;
const bounded = Math.min(exponential, maxMs);
const jitter = Math.floor(Math.random() * 250);
return bounded + jitter;
}Why
baseMsmeans the base delay everywhere in the function, as its name promises.- Each intermediate value has a name, which makes logging and testing each step straightforward.
- Code that reads the original input later (a log line, a guard) still gets the original input.
Steps
- Declare a local variable initialized from the parameter, named for its new meaning.
- Replace every assignment to the parameter, and every read after the first assignment, with the local.
- Compile and run the tests.
- If the local itself is reassigned for different purposes, apply Split Temporary Variable.
- Mark parameters
readonlywhere the language or a linter allows it (for example ESLintno-param-reassign).
Watch out
- Mutating the contents of an object parameter (
order.status = "paid") is a different issue. This technique is about rebinding the name. Mutating arguments may still be a design problem, but fix it separately. - If the language passes the parameter by reference on purpose (an
outorrefparameter), the reassignment is the function's contract. Change that contract only together with its callers, typically by returning the value instead. - Default-value normalization at the very top (
limit = limit ?? 50) is a common idiom. Converting it is still clearer, but it is low priority.
Related
- Helps with Long Method.
- Split Temporary Variable is the same idea for locals.
- Clears the way for Extract Method.
Agent checklist
- No parameter is reassigned after the change.
- Every read that used the modified value now reads the local.
- By-reference contracts (
ref,out,&) were preserved or changed together with callers. - Tests pass.
Treats these smells
See also
- Split Temporary VariableGive each distinct purpose its own variable when one local is reassigned to hold unrelated intermediate values.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- Replace Method with Method ObjectTurn a long method with tangled local variables into its own class, so the locals become fields and the body can be split freely.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.