Skip to content

Index Made of Coupling

You are introducing an abstraction — a wrapper service, a shared package, a folder convention, an aspect, a base class — and the honest reason is findability. You want to know where all the instances are on the day something changes. The abstraction is a place to stand so that later you can enumerate.

There is no index of your codebase’s concepts, so you build one out of the only material available: code structure. You pay for it in coupling, permanently, and you call the payment good design.

Cognitive and behavioral, borne by everyone who touches the code afterward — and by the organization’s ability to change, which is nobody’s line item and therefore invisible.

  • The abstraction usually fails at the exact moment it is supposed to pay out. You wrapped Google Analytics so you could swap it. HockeyApp does not fit GA’s interface. You update every call site anyway, having paid coupling for three years to buy nothing but the list.
  • Every consumer is now coupled to a shape that was invented for the migration, not for the work. Features that had nothing to do with each other share a signature.
  • The abstraction leaks the thing it was hiding. IRepository<T> grows IQueryable because someone needed a real query. Now you have coupling and no portability.
  • Shared packages built for conformance (“here is how we do Kerberos”) fuse four separable things — the decision, the implementation, the version, and the language — and consumers can only take all four. Every upgrade becomes a rolling synchronization across teams that share no release cadence.
  • Aspect-oriented approaches make the concern findable by gathering it, and destroy legibility at the point of use. You can read the method and not know what runs.
  • The extraction is defended by people who cannot state what it prevents, because the thing it prevents (“we won’t be able to find them”) has no name.
  • You cannot state what breaks if you don’t build the abstraction, other than “we won’t know where they all are.”
  • The justification is a hypothetical future swap of a dependency that has never been swapped.
  • The interface is being designed against exactly one implementation, and its shape is that implementation’s shape with the names changed.
  • Someone says “so we only have to change it in one place” and nobody asks whether one place is where the change belongs.
  • The abstraction is going into shared/ or common/ on the day it is written.
  • A wrapper with exactly one implementation, years old, whose interface mirrors that implementation exactly.
  • A shared conformance package whose consumers are pinned to different versions and cannot upgrade independently.
  • A migration that was supposed to be cheap because of an abstraction, and wasn’t — every call site changed anyway.
  • Code that everyone routes around: new features duplicate logic rather than extend the abstraction, because the abstraction’s shape does not fit and changing it is frightening.
  • You can answer “where is this concept used?” only by knowing which abstraction someone chose to build.
  • Separate findability from structure. These are two different needs and they have been conflated because only one of them had a tool. Ask which one you actually have. If the answer is findability, an index is the correct instrument — a record of where the concept is manifest, not a wrapper that forces every instance to route through one place.
  • Require the abstraction to name what it prevents, in falsifiable terms. “We have had three transposed-argument bugs at these call sites” is a reason. “We might swap providers” is a wish; ask when it last happened.
  • Do not confuse the experienced developer’s instinct with their proposed remedy. They are right that you will need to find these later. The remedy — wrap it now — is what loses them the argument, because it was the only remedy available. YAGNI and the senior developer are arguing about different things: findability versus structure. Separate them and the argument dissolves.
  • Defer the abstraction, capture the knowledge. Record that the concept exists and where it is manifest. Build the abstraction when there is a second consumer and a contract worth owning.
  • The abstraction has an intrinsic contract. A parser, a crypto primitive, a Luhn check, a financial calculation. The contract pre-exists any caller and stability is the value. See False Reuse.
  • A real second implementation exists now, not hypothetically. Two payment providers in production is not a wish.
  • The coupling is already paid for. Consolidating eight implementations inside one deployable owned by one team, shipped together, costs almost nothing — the release cadence is already shared. The same consolidation across thirty-two independently deployed services builds a synchronization trap. Consolidate where the coupling is already paid for, and nowhere else.
  • The abstraction buys isolation, not findability. A boundary that exists so failures don’t propagate is a different thing wearing a similar shape.

Several familiar critiques turn out to be one failure with different materials:

built fromexample
typeswrapper interfaces, IRepository around a database
packagesshared libraries as a conformance mechanism
directoriesshared/, common/, folder-per-concern conventions
weavingaspect-oriented programming
naming disciplineubiquitous language maintained by convention alone

Each is an index constructed out of the wrong material, and each is paid for in coupling. That is not five critiques; it is one, and stating it that way makes the alternative obvious — build the index out of index.

It also explains why the DRY orthodoxy is defensible and its usual remedy isn’t. Ask why duplication is bad and the honest answer is not the edit cost, it is that you cannot know where else to look. Centralization was never the diagnosis. It was the only available treatment for that specific blindness. Given a real index, the justification evaporates and what remains is a much narrower case for consolidation: actual shared fate.

Related: False Reuse and Sticky Contract describe abstractions that acquire unintended contracts. This entry describes abstractions built deliberately, for a reason that was never structural in the first place.