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

- kind: technique
- category: simplifying-method-calls
- canonical: https://aipatterns.dev/refactoring/techniques/remove-setting-method
- inspired-by: https://refactoring.guru/remove-setting-method

## 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

```ts
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

```ts
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](https://aipatterns.dev/refactoring/techniques/replace-constructor-with-factory-method.md).
- The inverse direction is adding a setter through [Encapsulate Field](https://aipatterns.dev/refactoring/techniques/encapsulate-field.md); do it only when the value must truly change.

## Related

- Treats [Data Class](https://aipatterns.dev/refactoring/smells/data-class.md), where fields are freely writable.
- Often paired with [Replace Constructor with Factory Method](https://aipatterns.dev/refactoring/techniques/replace-constructor-with-factory-method.md) and [Hide Method](https://aipatterns.dev/refactoring/techniques/hide-method.md).

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