A core class peppered with optional-behaviour conditionals the agent must trace on every method-edit to verify which features are active in a given configuration. Flag combinations are not statically enumerable; the agent must hold the option-space in working memory across every read.
One file per behaviour the agent reads in isolation; the core class is short and verifiable on its own. Composition order is statically observable at the construction site; the agent can reason about wrapper effects sequentially.
Before the refactoring
// One class accumulates optional embellishments via flags + inline conditionals.class HttpClient {constructor(options = {}) {this.baseUrl = options.baseUrl;this.timeout = options.timeout;this.retries = options.retries;this.logger = options.logger;this.authToken = options.authToken;}async get(path) {if (this.logger) this.logger.log(`GET ${path}`);let attempt = 0;while (true) {try {const headers = {};if (this.authToken) headers.Authorization = `Bearer ${this.authToken}`;const response = await fetch(this.baseUrl + path, {headers,signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined,});return await response.json();} catch (e) {if (attempt >= (this.retries ?? 0)) throw e;attempt++;}}}}
After the refactoring
// Each embellishment is a decorator wrapping the core client.class HttpClient {constructor(baseUrl) {this.baseUrl = baseUrl;}async get(path) {const response = await fetch(this.baseUrl + path);return await response.json();}}class LoggingClient {constructor(inner, logger) { this.inner = inner; this.logger = logger; }async get(path) {this.logger.log(`GET ${path}`);return this.inner.get(path);}}class RetryingClient {constructor(inner, retries) { this.inner = inner; this.retries = retries; }async get(path) {for (let attempt = 0; ; attempt++) {try { return await this.inner.get(path); }catch (e) { if (attempt >= this.retries) throw e; }}}}class AuthClient {constructor(inner, authToken) { this.inner = inner; this.authToken = authToken; }async get(path) {// adds Authorization header; simplified for illustrationreturn this.inner.get(path);}}const client = new RetryingClient(new AuthClient(new LoggingClient(new HttpClient('https://api.example.com'), logger),authToken,),3,);
The combinatorial flag-space defeats static reasoning — the agent cannot enumerate which combinations have been tested or which are reachable. Per-feature behaviour change requires editing the core class, which the agent then has to re-verify against all flag combinations.
Decorator chains spread the call-path across multiple files; the agent must traverse the chain to know what a single method call does. Wrapping-order is not statically declared anywhere; the agent must read the construction site to recover the intent.
Adding or removing an embellishment is one decorator file or one wiring edit at the construction site; the core class holds the base behavior without optional-feature conditionals, and per-feature tests load one decorator instead of every combination.
Five-deep decorator chains require the agent to load five definitions before reading the call. Stack traces obscure where in the chain a failure occurred; the agent's debugging cost goes up with depth. A composition-style API that names the intended capability set can be more legible than the raw chain.