A class is wrapped in Singleton machinery (`getInstance`, private constructor, static cache) but the singleton-ness is not load-bearing. Callers reach into the global accessor; tests can't isolate the class because the singleton instance leaks across test cases; dependencies are invisible from constructor signatures.
Singleton accessors (`Class.getInstance()`) hide the agent's view of which classes depend on the collaborator. The agent must grep the codebase for every static-accessor call to know the real dependency graph; test setup requires resetting global state between cases.
The class is a regular collaborator that callers receive through their constructors (or another explicit hand-off). Tests construct fresh instances per case; dependencies are visible at the type level; global state shrinks.
Constructor signatures carry every dependency the class uses; the agent reads one signature to enumerate what the class touches instead of grepping for static accessor calls across the codebase.
Before the refactoring
// Singleton machinery for what is effectively a regular collaborator.class Logger {static instance = null;static getInstance() {if (!Logger.instance) {Logger.instance = new Logger();}return Logger.instance;}constructor() {this.entries = [];}log(message) {this.entries.push({ time: Date.now(), message });}}// Client code reaches into the global accessor.function processOrder(order) {Logger.getInstance().log(`Processing order ${order.id}`);// ...}
After the refactoring
// Regular class; callers receive a logger through their constructor.class Logger {constructor() {this.entries = [];}log(message) {this.entries.push({ time: Date.now(), message });}}class OrderProcessor {constructor(logger) {this.logger = logger;}processOrder(order) {this.logger.log(`Processing order ${order.id}`);// ...}}
Singletons hide dependencies and leak state across tests. The static cache survives between test cases, producing flaky failures that depend on test ordering. New callers couple to the global accessor instead of declaring what they need.
Static accessors defeat static call-graph analysis at the dependency level; the agent cannot infer from a class's interface what it actually uses. Test-order flakiness from shared static state is invisible to local reasoning and only surfaces under CI.
Inlining a Singleton pushes dependency wiring outward — every caller now needs to know how to obtain the collaborator. Without a composition root, the wiring can scatter; reasonable Singletons (true cross-cutting infrastructure like a thread-safe metrics registry) may legitimately want the global access pattern.
Inlining pushes wiring code outward; the agent must reason about a composition root or DI container to verify production behaviour. Without one, the inlining may produce duplicated wiring across callers that the agent now has to verify match.
Tests construct the class with stub collaborators; production wiring lives at the composition root; the dependency graph reads from constructor signatures. The static-cache class of flakiness disappears.
Every dependency the class uses appears in its constructor signature; the agent enumerates dependencies from one file load instead of grepping for static-accessor calls across the codebase.
Inlining a Singleton without setting up a composition root leaves every caller responsible for constructing or finding the collaborator. The result can be worse than the Singleton — dozens of `new Logger()` instances each with their own state, accidentally fragmenting what was conceptually one log.
Inlining without first establishing a composition root produces `new Logger()` calls scattered across consumer files, each creating an independent instance; generated code that assumes a shared logger picks up an isolated one and the divergence ships.