Refactoring techniqueSimplifying Method Calls
Introduce Parameter Object
Replace a group of parameters that always travel together with one object type, then move behavior on that group into it.
Problem
Several methods take the same cluster of parameters, such as start and end, or amount and currency. The values only make sense together. Callers can pass them in the wrong order, and the rules that link them (start before end) are rechecked or forgotten in each method.
Solution
Create a type that holds the group, replace the parameters with one parameter of that type, and move the logic that belongs to the group into it.
Before
function countBookings(roomId: string, from: Date, to: Date): Promise<number> {
if (from >= to) throw new RangeError("from must be before to");
return db.bookings.count({ roomId, startsAt: { gte: from, lt: to } });
}
function revenue(roomId: string, from: Date, to: Date, currency: string): Promise<number> {
if (from >= to) throw new RangeError("from must be before to");
return db.payments.sum({ roomId, currency, paidAt: { gte: from, lt: to } });
}
await revenue("r-12", new Date("2026-01-01"), new Date("2026-02-01"), "EUR");After
class DateRange {
constructor(readonly from: Date, readonly to: Date) {
if (from >= to) throw new RangeError("from must be before to");
}
toFilter(): { gte: Date; lt: Date } {
return { gte: this.from, lt: this.to };
}
}
function countBookings(roomId: string, period: DateRange): Promise<number> {
return db.bookings.count({ roomId, startsAt: period.toFilter() });
}
function revenue(roomId: string, period: DateRange, currency: string): Promise<number> {
return db.payments.sum({ roomId, currency, paidAt: period.toFilter() });
}
await revenue("r-12", new DateRange(new Date("2026-01-01"), new Date("2026-02-01")), "EUR");Why
- Signatures are shorter and say what they work with.
- The invariant is enforced once, at construction, so an invalid range cannot exist.
- The new type attracts behavior (
contains,overlaps,lengthInDays) that was scattered.
Steps
- Create an immutable type with the grouped fields. Run the tests.
- Add a parameter of the new type to one method, keeping the old parameters. Update its callers to pass both. Run the tests.
- Inside the method, read from the new object instead of each old parameter, then remove the old parameters. Run the tests.
- Repeat for every method that takes the same group.
- Move duplicated logic about the group (validation, conversion) into the new type. Run the tests.
Watch out
- Do not group parameters that only coincidentally appear together in one method. The group should show up in several signatures or have rules linking its members.
- Make the object immutable. A mutable parameter object shared between callers leads to surprising action at a distance.
- If an existing object already holds the values, use Preserve Whole Object instead of creating a new type.
- In JavaScript and TypeScript, a plain options object (
{ from, to }) is a lighter first step when you only need named arguments, not invariants. - Unwrapping a parameter object back into separate parameters has no named technique; do it only when the type stops pulling its weight.
Related
- Fixes Long Parameter List, Data Clumps and Primitive Obsession.
- Alternative: Preserve Whole Object.
- Next step: Move Method into the new type, as with Extract Class.
Agent checklist
- The same group of parameters appears in more than one signature, or its members share a rule.
- The new type is immutable and validates its invariants on construction.
- Each method was migrated separately with tests green in between.
- Duplicated validation for the group was moved into the type and removed from methods.
Treats these smells
- Long Parameter ListA function takes so many parameters that callers mix up their order and every new option changes every call site.
- Data ClumpsThe same group of variables travels together through fields and parameters, signaling a missing object that should own them.
- 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.
See also
- Preserve Whole ObjectPass the object itself instead of several values pulled out of it, so the method signature stays stable as needs change.
- Extract ClassSplit a class that does two jobs by moving one job's fields and methods into a new class it holds a reference to.
- Move MethodMove a method to the class whose data it uses most, and leave a delegating call or update the callers.
- Add ParameterGive a method a new parameter when it needs data it cannot reach, migrating callers through a temporary overload or default value.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.