Skip to content
aipatterns.dev

Refactoring techniqueSimplifying Method Calls

Remove Setting Method

Delete the setter for a field that should only be set at creation, and assign it in the constructor so it cannot change later.

Problem

A field is meant to be fixed once the object exists: an ID, a tenant, the currency of an invoice. A public setter lets any code change it later. Changing it can corrupt related data, and readers cannot tell whether the value is stable.

Solution

Set the field through the constructor (or a factory), make it read-only, and delete the setter.

Before

TypeScript
class Invoice {
  private currency = "USD";
  private lines: InvoiceLine[] = [];

  setCurrency(currency: string): void {
    this.currency = currency;
  }

  getCurrency(): string {
    return this.currency;
  }

  addLine(line: InvoiceLine): void {
    this.lines.push(line);
  }
}

const invoice = new Invoice();
invoice.setCurrency("EUR");
invoice.addLine({ description: "Consulting", amountCents: 120_000 });

After

TypeScript
class Invoice {
  private lines: InvoiceLine[] = [];

  constructor(readonly currency: string) {}

  addLine(line: InvoiceLine): void {
    this.lines.push(line);
  }
}

const invoice = new Invoice("EUR");
invoice.addLine({ description: "Consulting", amountCents: 120_000 });

Why

  • An invoice's currency can no longer change after lines priced in the old currency were added.
  • Readers and the compiler both know the field is stable, so no defensive re-checks are needed.
  • Immutable fields are safe to share and to use as map keys.

Steps

  1. Find every caller of the setter. Confirm each call happens right after construction, before the object is used.
  2. Add a constructor parameter for the field. Have existing callers pass the value they used to set. Run the tests.
  3. Remove the setter calls that follow construction. Run the tests.
  4. Delete the setter and mark the field readonly (or final, val). The compiler will flag any remaining writes. Run the tests.
  5. If the field is set internally in a later step, such as an ID assigned on save, keep that write private and named for its purpose rather than a general setter.

Watch out

  • Do not remove a setter for state that legitimately changes, like an order's status. Replace it with intention-revealing methods (ship(), cancel()) instead.
  • Frameworks such as ORMs, serializers and dependency injectors may need setters or a no-argument constructor. Check whether they can use constructor binding or private field access before removing.
  • If construction now takes many arguments, use a factory or builder instead of a long constructor; see Replace Constructor with Factory Method.
  • The inverse direction is adding a setter through Encapsulate Field; do it only when the value must truly change.

Agent checklist

  • All setter calls occurred during initialization only.
  • Frameworks that hydrate this class still work without the setter.
  • The field is marked readonly so the compiler blocks future writes.
  • State that does change goes through named domain methods, not a setter.

Treats these smells

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.