top of page

Introduce Parameter Object — Refactoring Patterns, Part 7

Shawn West
Jul 30
2 min read
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)

  1. Identify cluster of parameters

  2. Create class

  3. Replace function signature

  4. Update callers (build object instead of passing args)

  5. Run tests

  6. 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

Part of the Refactoring Patterns learning path.

bottom of page