Skip to content
aipatterns.dev

Refactoring techniqueComposing Methods

Replace Temp with Query

Move the expression behind a temporary variable into its own method so every method in the class can ask for the value.

Problem

A method computes a value into a local variable and uses it later. Other methods in the class compute the same value their own way, and the local makes the long method hard to split because every fragment depends on it.

Solution

Extract the expression into a method (a query) with no side effects, and replace the variable with calls to it. The value is now available anywhere in the class and does not tie fragments together.

Before

TypeScript
class Shipment {
  constructor(private parcels: Parcel[], private zone: Zone) {}

  label(): string {
    const billableKg = Math.max(
      this.parcels.reduce((s, p) => s + p.weightKg, 0),
      this.parcels.reduce((s, p) => s + p.volumeCm3, 0) / 5000,
    );
    const fee = billableKg * this.zone.ratePerKg;
    return `${this.zone.code} ${billableKg.toFixed(1)}kg ${fee.toFixed(2)}`;
  }

  needsFreightCarrier(): boolean {
    const actualKg = this.parcels.reduce((s, p) => s + p.weightKg, 0);
    const volumetricKg = this.parcels.reduce((s, p) => s + p.volumeCm3, 0) / 5000;
    return Math.max(actualKg, volumetricKg) > 70;
  }
}

After

TypeScript
class Shipment {
  constructor(private parcels: Parcel[], private zone: Zone) {}

  label(): string {
    return `${this.zone.code} ${this.billableKg().toFixed(1)}kg ${this.fee().toFixed(2)}`;
  }

  needsFreightCarrier(): boolean {
    return this.billableKg() > 70;
  }

  private billableKg(): number {
    const actualKg = this.parcels.reduce((s, p) => s + p.weightKg, 0);
    const volumetricKg = this.parcels.reduce((s, p) => s + p.volumeCm3, 0) / 5000;
    return Math.max(actualKg, volumetricKg);
  }

  private fee(): number {
    return this.billableKg() * this.zone.ratePerKg;
  }
}

Why

  • The billable-weight rule lives in one place. A change to the volumetric divisor touches one line.
  • label lost its locals, so further extraction is trivial.
  • Queries document themselves in IDE outlines and can be tested.

Steps

  1. Check the variable is assigned once. If it is assigned in several places, apply Split Temporary Variable first.
  2. Extract the right-hand side into a private method. Make sure the method does not modify state.
  3. Run the tests.
  4. Replace each read of the variable with a call to the new method, or apply Inline Temp. Run the tests.
  5. Replace duplicate computations elsewhere in the class with the query.

Watch out

  • Repeated calls repeat the computation. That is fine for cheap expressions. For costly ones (database reads, large reductions inside a loop), keep the variable or memoize, and measure first.
  • Don't apply when the variable captures a snapshot that must not change, such as a timestamp taken at the start of a transaction.
  • The query must be side-effect free. If the expression mutates anything, separate the mutation first.
  • The opposite move is to keep or reintroduce a local with Extract Variable when the value is only meaningful in one method.

Agent checklist

  • The variable was assigned once and the query has no side effects.
  • The value cannot change between calls within the original method, or a change is acceptable.
  • Performance of repeated calls was considered for non-trivial expressions.
  • Duplicate computations elsewhere now use the query.
  • Tests pass.

Treats these smells

See also

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