Introduce Parameter Object — Refactoring Patterns, Part 7
Refactoring Patterns · Part 7
A function that takes eight positional parameters is one nobody can call correctly without checking the signature — and the day someone swaps two arguments of the same type, you get a silent bug. Introduce Parameter Object bundles related arguments into one named thing. This walks through the refactor, plus where it earns its keep (validation, value objects) and where it's overkill.
When the same parameters keep appearing together, they're a thing. Name it.
Step 1: The Smell (15 min)
function calculateShipping(
fromStreet, fromCity, fromZip, fromCountry,
toStreet, toCity, toZip, toCountry,
weight, dimensions
) { ... }
Eight parameters group into two natural objects.
Step 2: After (15 min)
class Address {
constructor(public street, public city, public zip, public country) {}
}
function calculateShipping(from: Address, to: Address, weight, dimensions) { ... }
Cleaner signature. Reuse across functions. Type safety.
Step 3: When to Apply (15 min)
Same parameters appear in multiple functions
Many parameters (>3)
Parameters travel together logically
You'd add more parameters in the future
Step 4: Validation Moves In (15 min)
With parameter object:
class Address {
constructor(public street, public city, public zip, public country) {
if (!isValidZip(zip, country)) throw new Error("Invalid zip for country")
}
}
Validation in one place. Can't construct invalid address.
Step 5: Value Object (15 min)
If parameter object becomes immutable + behavior-rich: it's a value object.
class Money {
constructor(public readonly amount: number, public readonly currency: string) {}
add(other: Money): Money { ... }
multiply(factor: number): Money { ... }
}
Step 6: The Steps (15 min)
Identify cluster of parameters
Create class
Replace function signature
Update callers (build object instead of passing args)
Run tests
Commit
IDE supports: "Introduce Parameter Object."
Step 7: Naming (15 min)
Avoid:
Params
Args
Data
Use domain concept:
Address
DateRange
Money
OrderQuery
Step 8: Optional Fields (15 min)
interface FilterOptions {
customer?: string
minDate?: Date
maxDate?: Date
status?: OrderStatus
}
function findOrders(opts: FilterOptions) { ... }
Named optional fields. Callers omit irrelevant ones.
Better than ten boolean / null parameters.
Step 9: When NOT (15 min)
Two parameters that aren't related
Throwaway helper function
Adding object adds more noise than clarity
If grouping feels forced: don't.
Step 10: Combine With (15 min)
After Parameter Object:
Add methods (validation, derived data)
Becomes Value Object
Reuse across modules
Often a stepping stone to richer abstractions.
What You Just Did
Introduce Parameter Object: the smell, after, when to apply, validation moves in, value object, the steps, naming, optional fields, when not, combine with.
Common Failure Modes
Generic name (Params, Args). Doesn't add meaning.
Object for 2 unrelated params. Friction.
Forget validation. Same bug surface.
Mutable object. Surprise mutations.
Pass everywhere; need only one field. Coupling.
Continue the Refactoring Patterns path
Previous — Part 6: Rename Symbol Safely
Next — Part 8: Split Phase
Part of the Refactoring Patterns learning path.


