Refactoring techniqueSimplifying Method Calls
Rename Method
Change a method's name to say what it does, using a forwarding alias when callers cannot all be updated at once.
- Also known as
- Change Function Declaration, Rename Function
Problem
A method's name does not describe what it does. It may describe how it works, use a stale term from an old version of the domain, or be so vague (process, handle, doIt) that every reader opens the body to find out. Comments at call sites try to compensate.
Solution
Give the method a name that states its effect or result in domain terms, and update every caller. For published APIs, keep the old name as a deprecated alias for a transition period.
Before
class SubscriptionService {
// Moves the plan to the next tier and prorates the charge
async chk(sub: Subscription, plan: PlanId): Promise<Invoice> {
const credit = this.unusedCredit(sub);
sub.plan = plan;
return this.billing.charge(sub.customerId, this.priceOf(plan) - credit);
}
}
await subscriptions.chk(sub, "pro");After
class SubscriptionService {
async upgradeWithProration(sub: Subscription, plan: PlanId): Promise<Invoice> {
const credit = this.unusedCredit(sub);
sub.plan = plan;
return this.billing.charge(sub.customerId, this.priceOf(plan) - credit);
}
/** @deprecated Use upgradeWithProration. Remove after v4. */
chk(sub: Subscription, plan: PlanId): Promise<Invoice> {
return this.upgradeWithProration(sub, plan);
}
}
await subscriptions.upgradeWithProration(sub, "pro");Why
- Code is read far more than written. A good name removes a trip into the body for every reader.
- The explanatory comment becomes unnecessary.
- Consistent names across classes reveal duplication and let you unify interfaces.
Steps
- Check whether the method is declared in a superclass, subclass or interface. A rename must cover every declaration in the hierarchy.
- If your tooling supports a safe rename (an IDE or language server rename), use it and run the tests. You are done.
- Otherwise, add a new method with the new name and copy the body into it. Make the old method delegate to the new one. Run the tests.
- Update callers to the new name, a few at a time, running the tests as you go.
- When nothing calls the old name, delete it. For a public API, mark it deprecated and delete it in a later release.
Watch out
- Text search misses dynamic calls: string-based dispatch, reflection, RPC method names, serialized event names, template bindings. Search for the string, not only the symbol.
- Renaming a method that is part of a public package, REST route or message contract is a breaking change. Keep the alias.
- Do not rename to describe the implementation (
loopAndSumCharges). Name the outcome (totalCharges). - If you cannot find a good name, the method may do two things. Try Separate Query from Modifier or Extract Method first.
- Rename is its own inverse.
Related
- Helps with Alternative Classes with Different Interfaces by aligning names, and removes Comments that explain bad names.
- Often done together with Add Parameter or Remove Parameter.
Agent checklist
- All declarations in the class hierarchy and interfaces were renamed together.
- String-based references (reflection, routes, configs, mocks) were searched for and updated.
- Public APIs keep a deprecated alias.
- The new name describes the result, not the mechanics.
Treats these smells
See also
- Add ParameterGive a method a new parameter when it needs data it cannot reach, migrating callers through a temporary overload or default value.
- Remove ParameterDelete a parameter the method body no longer uses, updating every caller and override so the signature states only real dependencies.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- Separate Query from ModifierSplit a method that both returns a value and changes state into a side-effect-free query and a separate command.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.