Simplifying Method Calls — Junior Level¶
Source: refactoring.guru/refactoring/techniques/simplifying-method-calls
Table of Contents¶
- What this category is about
- Real-world analogy
- The 14 techniques at a glance
- Rename Method
- Add Parameter
- Remove Parameter
- Separate Query from Modifier
- Parameterize Method
- Replace Parameter with Explicit Methods
- Preserve Whole Object
- Replace Parameter with Method Call
- Introduce Parameter Object
- Remove Setting Method
- Hide Method
- Replace Constructor with Factory Method
- Encapsulate Downcast
- Replace Error Code with Exception / Replace Exception with Test
- Mini Glossary
- Review questions
What this category is about¶
Simplifying Method Calls is about cleaning up method interfaces — names, parameter lists, return shapes, error reporting. Where Composing Methods restructures the inside and Moving Features changes placement, this category fixes how methods are exposed.
The smells these cure:
- Long Parameter List — Introduce Parameter Object, Preserve Whole Object, Replace Parameter with Method Call.
- Comments — Rename Method.
- Inappropriate Intimacy — Hide Method, Remove Setting Method.
- Switch Statements — Replace Constructor with Factory Method.
- Speculative Generality — Remove Parameter.
- Duplicate Code — Parameterize Method.
Key idea: the method's signature is its public face. Bad names, too many parameters, or hidden side effects make the API hostile. These refactorings make APIs honest and ergonomic.
Real-world analogy¶
Bad form labels¶
A government form asks: - "Field 1: Number" - "Field 2: Date (numeric)" - "Field 3: Region (1-50)" - "Field 4: Subject" - "Field 5: Other Notes"
Nothing has a meaningful name. A user trying to fill it out has to read the instructions repeatedly to remember which field is what.
Same form with names: - "Phone number" - "Date of birth" - "Country code" - "Reason for application" - "Additional notes"
Same data, drastically more usable.
That's exactly what Rename Method, Introduce Parameter Object, and Replace Parameter with Explicit Methods do at the code level.
The 14 techniques at a glance¶
| Technique | What it does |
|---|---|
| Rename Method | Better name |
| Add Parameter | Method needs more info |
| Remove Parameter | Parameter no longer needed |
| Separate Query from Modifier | A method that returns a value should not have side effects |
| Parameterize Method | Several similar methods → one parameterized |
| Replace Parameter with Explicit Methods | Opposite: parameter triggers different paths → split |
| Preserve Whole Object | Pass the object instead of its fields |
| Replace Parameter with Method Call | Caller has access; let callee compute |
| Introduce Parameter Object | Group parameters into an object |
| Remove Setting Method | Field shouldn't change after construction |
| Hide Method | Make method private/protected |
| Replace Constructor with Factory Method | Need named construction or polymorphic creation |
| Encapsulate Downcast | Push downcast inside the method that returns the value |
| Replace Error Code with Exception / Replace Exception with Test | Pick the right error-reporting style |
Rename Method¶
What it does¶
Give a method a better name. Sounds trivial — and is the most-used refactoring in any IDE.
Before¶
After¶
Why¶
- The name is the documentation.
- A bad name forces every caller to read the body.
- IDE renames are safe — every caller updates atomically.
Mechanics (Fowler)¶
- Check if the method is in a superclass — rename in the whole hierarchy.
- Declare a new method with the new name. Copy body. Old method delegates to new.
- Update every caller (most IDEs do this automatically).
- Remove the old method.
Anti-pattern¶
Renaming the same method 4 times in a quarter as opinions shift. Pick a name with the team; commit; move on.
Add Parameter¶
What it does¶
A method needs more information from the caller.
Before¶
public Discount applyDiscount(Customer c) {
if (c.isLoyal()) return Discount.of(0.10);
return Discount.NONE;
}
After¶
public Discount applyDiscount(Customer c, Promotion p) {
if (c.isLoyal()) return Discount.of(0.10);
if (p != null && p.isActive()) return p.discount();
return Discount.NONE;
}
Cautions¶
- Update all callers (IDE will surface them).
- For public APIs, this is breaking — overload (keep old, add new with extra param) until consumers migrate.
When to consider alternatives¶
If you're adding a 5th parameter, consider Introduce Parameter Object instead.
Remove Parameter¶
What it does¶
A parameter that's no longer needed adds noise. Remove it.
Before¶
After¶
Cautions¶
- Don't remove if some code paths still use it (look at every caller's argument).
- For public APIs, consider deprecating before removing.
When does this happen¶
- A feature was reverted but the parameter remained.
- A parameter was added speculatively ("might need it later") and never used.
Separate Query from Modifier¶
What it does¶
A method that returns a value AND has side effects is hard to use safely. Split it.
Before¶
String getTotalOutstandingAndSetReadyForSummary() {
double total = computeTotal();
readyForSummary = true; // side effect
return total;
}
After¶
String getTotalOutstanding() { return computeTotal(); }
void setReadyForSummary() { readyForSummary = true; }
Why¶
- Callers that want the total but not the side effect can call only the query.
- Test cases for each are independent.
- Idempotency: calling the query twice gives the same answer; the modifier is explicit.
When NOT¶
- The side effect is intrinsic (e.g.,
next()on an iterator — returns and advances). That's domain. - The side effect is logging/auditing — usually fine inside a query.
Parameterize Method¶
What it does¶
Several methods that differ only in a value → one method with a parameter.
Before¶
double tenPercentRaise() { return salary * 1.10; }
double fivePercentRaise() { return salary * 1.05; }
double fifteenPercentRaise() { return salary * 1.15; }
After¶
When NOT¶
- The variants are conceptually different things, not values of the same operation. (
saveAsPdf()vs.saveAsHtml()aren't really "save with format").
Replace Parameter with Explicit Methods¶
What it does¶
The opposite. A parameter that triggers entirely different code paths is hiding two methods.
Before¶
public Result process(Order o, boolean express) {
if (express) return expressFlow(o);
return standardFlow(o);
}
After¶
public Result processExpress(Order o) { return expressFlow(o); }
public Result processStandard(Order o) { return standardFlow(o); }
When¶
- The boolean parameter selects entirely different behavior.
- Callers always know statically which they want.
When NOT¶
- The parameter is genuinely runtime-determined.
- Callers select dynamically based on data.
See OO Abusers — Switch Statements on the "boolean parameter trap."
Preserve Whole Object¶
What it does¶
double low = daysTempRange.getLow();
double high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
→
Why¶
- Caller doesn't have to know
getLow()/getHigh(). - If TempRange grows a
meanfield that the plan also wants to use, no parameter list change needed.
When NOT¶
- The callee doesn't need the whole object — passing it overspecifies the dependency.
- Test setup becomes harder (you need to construct a full TempRange).
Trade-off: Preserve Whole Object reduces coupling at the call site; increases coupling at the callee. Pick based on direction of growth.
Replace Parameter with Method Call¶
What it does¶
The caller passes a value the callee can compute itself. Let the callee compute it.
Before¶
double basePrice = quantity * itemPrice;
double discount = discountFor(basePrice, customerType());
double total = basePrice - discount;
After¶
private double basePrice() { return quantity * itemPrice; }
double discount = discountFor(customerType());
private double discountFor(CustomerType type) {
if (basePrice() > 1000) return basePrice() * 0.10;
...
}
When¶
- Both methods are on the same class.
- The parameter is derivable from existing state.
Caution¶
If the callee calls the query multiple times, watch perf (see Replace Temp with Query in Composing Methods).
Introduce Parameter Object¶
What it does¶
A parameter list of 4+ items, especially when the same group recurs, becomes one object.
Before¶
After¶
Why¶
- The signature is shorter and more readable.
- The
DateRangetype can grow methods (overlaps,contains,daysBetween). - Type safety: can't pass
(end, start)by mistake.
When NOT¶
- The "parameters" don't naturally cluster (passing
name,total,currency,customerIdmay not be one concept). - The cluster only shows up once.
Java records (Java 14+) make this nearly free.
Remove Setting Method¶
What it does¶
If a field shouldn't change after construction, remove its setter.
Before¶
class Customer {
private String id;
public Customer(String id) { this.id = id; }
public void setId(String id) { this.id = id; } // ❌
}
After¶
Why¶
- An entity's id shouldn't change. The setter was a footgun.
- Immutability simplifies reasoning; thread safety becomes natural.
When¶
- Identifiers (id, email-as-key, social security number).
- Domain invariants (an Order's customer can't change after submission).
Hide Method¶
What it does¶
A method that's used only internally is made private (or package-private).
Why¶
- Reduced API surface = less coupling.
- Future refactorings don't break external callers.
- Callers can't depend on what they can't see.
Mechanics¶
- Find every caller.
- If all callers are within the class → private.
- If callers are in the same package → package-private.
- Otherwise: don't hide; the method is genuinely public.
Replace Constructor with Factory Method¶
What it does¶
becomes
Why¶
- Factory methods have names.
- They can return subclasses or cached instances.
- They can validate or transform inputs.
Before¶
After¶
abstract class Employee {
public static Employee createEngineer() { return new Engineer(); }
public static Employee createManager() { return new Manager(); }
}
class Engineer extends Employee {}
class Manager extends Employee {}
Modern alternatives¶
static of(...)is the idiomatic Java naming.- Java records often use named static factories.
- Kotlin:
companion object { fun create() = ... }. - Python:
@classmethod.
When NOT¶
- The class is a value object with one obvious constructor.
- Adding factory methods scatters construction logic without adding clarity.
See Creational Patterns.
Encapsulate Downcast¶
What it does¶
A method returns a wide type, callers downcast.
Before¶
public Object lastReading() { return readings.last(); }
// Caller:
Reading r = (Reading) station.lastReading();
After¶
public Reading lastReading() { return (Reading) readings.last(); }
// Caller:
Reading r = station.lastReading();
Why¶
- One downcast in one place instead of N.
- The downcast can be replaced by generics later.
Modern alternative¶
Use generics:
No downcast needed.
Replace Error Code with Exception / Replace Exception with Test¶
Replace Error Code with Exception¶
int withdraw(double amount) {
if (amount > balance) return -1; // ❌ error code
balance -= amount;
return 0;
}
becomes
void withdraw(double amount) {
if (amount > balance) throw new InsufficientFundsException();
balance -= amount;
}
Replace Exception with Test¶
The opposite. If the "error" is expected and easy to check, don't throw — let the caller check.
double getValueForPeriod(int periodNumber) {
try { return values[periodNumber]; }
catch (ArrayIndexOutOfBoundsException e) { return 0; }
}
becomes
double getValueForPeriod(int periodNumber) {
if (periodNumber >= values.length) return 0;
return values[periodNumber];
}
Picking between¶
| Approach | When |
|---|---|
| Exception | The error is rare AND can't easily be detected by the caller |
| Test (return value or check) | The error is expected, frequent, or cheap to check |
Exceptions are for exceptional conditions. Don't use them as glorified gotos.
Modern Go: errors as values (always returned). Modern Rust:
Result<T, E>. Both encourage explicit checking over throwing.
Mini Glossary¶
- Query method — returns a value, no side effects (pure).
- Modifier — changes state, possibly returns void.
- Factory method — static method that constructs an instance, possibly polymorphic.
- Parameter Object — a value type bundling related parameters.
- Whole Object — the entire object reference, vs. some of its fields.
- Downcast —
(Subtype) reference— a runtime type assertion.
Review questions¶
- Why is Rename Method the most-used refactoring?
- When should Add Parameter become Introduce Parameter Object?
- What's the difference between Parameterize Method and Replace Parameter with Explicit Methods?
- When does Preserve Whole Object hurt?
- Why is Separate Query from Modifier important?
- When is Replace Constructor with Factory Method right?
- What does Encapsulate Downcast achieve?
- When should errors be exceptions vs. return values?
- What's the boolean parameter trap?
- Why is Hide Method a good default discipline?
Next¶
- middle.md — when, trade-offs, language nuances.
- senior.md — API evolution, library design, deprecation.
- professional.md — JIT, dispatch, allocation.
- Practice: tasks.md, find-bug.md, optimize.md, interview.md.