Symptom

Multiple construction paths the agent scans in parallel to confirm field initialization is consistent across them. Behavioral preservation on every field-related edit requires verifying every path, multiplying the agent's by path count and the of pulling each construction body into context.

Goal

One construction path the agent reads to know what a fully-initialized object looks like; all other paths are one-line delegations the agent skips past during reasoning. The agent's holds the canonical constructor; the count drops because field-set drift is impossible by construction, and verifying any one path verifies the family.

Before the refactoring

class Loan {
static newTermLoan(commitment, customer, maturity) {
const loan = new Loan();
if (commitment < 0) throw new Error('commitment must be non-negative');
loan.commitment = commitment;
loan.customer = customer;
loan.maturity = maturity;
loan.expiry = null;
loan.unusedPercentage = 0.0;
return loan;
}
static newRevolver(commitment, customer, expiry) {
const loan = new Loan();
if (commitment < 0) throw new Error('commitment must be non-negative');
loan.commitment = commitment;
loan.customer = customer;
loan.maturity = null;
loan.expiry = expiry;
loan.unusedPercentage = 1.0;
return loan;
}
static newAdvisedLine(commitment, customer, expiry) {
const loan = new Loan();
if (commitment < 0) throw new Error('commitment must be non-negative');
loan.commitment = commitment;
loan.customer = customer;
loan.maturity = null;
loan.expiry = expiry;
loan.unusedPercentage = 0.5;
return loan;
}
}

After the refactoring

class Loan {
constructor(commitment, customer, expiry, maturity, unusedPercentage) {
if (commitment < 0) throw new Error('commitment must be non-negative');
this.commitment = commitment;
this.customer = customer;
this.expiry = expiry;
this.maturity = maturity;
this.unusedPercentage = unusedPercentage;
}
static newTermLoan(commitment, customer, maturity) {
return new Loan(commitment, customer, null, maturity, 0.0);
}
static newRevolver(commitment, customer, expiry) {
return new Loan(commitment, customer, expiry, null, 1.0);
}
static newAdvisedLine(commitment, customer, expiry) {
return new Loan(commitment, customer, expiry, null, 0.5);
}
}
Example source: Adapted from Joshua Kerievsky's Loan-class example in Refactoring to Patterns (Addison-Wesley, 2004), chapter 6. The Java original used overloaded constructors; this JavaScript translation uses static creation methods delegating to one canonical constructor — same shape, same single-point-of-initialization payoff.
Pressure

N construction paths × M fields = N×M cells the agent verifies match on every field-related edit. Field additions cascade across all paths; per-path duplication consumes context budget that could be spent on the calling code. The agent's multiplies with path count, and a partial update produces a half-initialized object that targeted tests may not exercise.

Tradeoff

A long canonical parameter list is itself a tax — the agent remembers positional argument order on every reading of a delegating factory. Wrong-position bugs become subtler than missing-field bugs; the agent's rises during the consolidation itself because every existing call site must be confirmed to route through the canonical constructor.

Relief

Each variant constructor delegates to the canonical one; adding a new field touches the canonical constructor once and every variant inherits the change, and tests against the canonical body cover all variants transitively. The agent's per field edit collapses to one path, and verification surface contracts from N paths to one.

Trap

The canonical constructor grows into a many-parameter signature where the agent loses track of which combinations are legal. Context cost moves from per-path duplication to per-parameter combination explosion; a parameter object or named-argument shape becomes overdue, and context-window load rises with each new parameter added without a name boundary.