Compare
Symptom
Human

Multiple ways to construct an object — overloaded constructors, factory methods, copy helpers — each repeat field assignments, defaults, and validation. Adding a new field forces edits in every construction path, and a field missed in one path silently produces partly-initialized objects. The team's compounds across paths no test inspects together.

Agent

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
Human

A single canonical constructor owns all field assignment and validation. Every other construction path is a one-line delegation that supplies the variant-specific arguments. Adding a new field is a one-place change; the team's drops from N parallel edits to one, and between construction-as-orchestration and construction-as-initialization becomes structural.

Agent

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
Human

Constructor duplication is Shotgun Surgery on the field set of a class. Each new variant adds a parallel copy of the initialization rules; subtle divergence between paths is easy to miss in review and easy to ship to production unnoticed. The team's compounds across every variant that has its own way to forget a field.

Agent

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
Human

Chaining couples every construction path to the canonical constructor's parameter list, and that list grows as variants accumulate. Call sites pass null or default sentinels for parameters that don't apply to their variant, eroding intention-revealing names at the call site. The of decoding which parameter applies to which variant rises with the signature's length.

Agent

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
Human

One place to read what 'constructing a Loan' means; new fields land in one constructor; each variant reads as the parameters it cares about. Validation runs once per construction, not once per path. The team's per construction site drops because the canonical body is the only authority, and review focuses on the variant-specific argument list.

Agent

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
Human

The canonical constructor grows into a Long Parameter List as variants accumulate. Readers no longer know which parameters matter for which variant, and the centralization stops paying — at that point Replace Constructors With Creation Methods or a parameter object becomes the next move. The cure adds when the canonical signature grows beyond what the variant set justifies.

Agent

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.