Sandi Metz named the wrong abstraction
The wrong abstraction fails because systems resolve trust once by reference and never revalidate whether that reference still means what it did.
An abstraction resolves many concrete instances to one definition. That is its entire purpose. Several call sites that once each carried their own copy of a behaviour now point at a single shared function, and the system treats that one definition as authoritative for all of them. In 2016 Sandi Metz gave the failure mode of this arrangement a name in a talk titled ‘The Wrong Abstraction’, and reduced the guidance to a single line: prefer duplication over the wrong abstraction. The principle she was arguing against, DRY, Do not Repeat Yourself, had been treated since ‘The Pragmatic Programmer’ in 1999 as close to a law of competent engineering.
What the abstraction actually does is narrower than the reputation of DRY suggests. It does not verify that two call sites mean the same thing. It observes that they look the same, and it binds them to one definition. From that moment the definition is a reference. Every caller holds a pointer to it, not a copy of the reasoning that produced it. The abstraction authenticates a shape. It does not authenticate a purpose. It confirms that the code passing through it fits the signature it expects, and nothing beyond that.
This is a claim about what the system resolves and when. It resolves the meaning of the shared code once, at the moment the abstraction is created, and it distributes that single resolution to every point that references it. The definition becomes the source of truth not because it is true, but because it is the thing being pointed at. Correctness at the call sites is now a property inherited from one location. The abstraction is a concentration of trust into a single reference, and it holds regardless of whether the instances it serves still share the reason they were merged.
The trust model underneath DRY is that sameness of form implies sameness of meaning. The assumption was that if 2 pieces of code are identical, they are identical for the same reason, and that reason will continue to hold for both. Duplication was treated as the defect and the shared abstraction as the correction. Underneath that correction sits a belief that the shared definition is a faithful proxy for every requirement that currently routes through it.
That belief extends across time as well as across call sites. The abstraction assumes that trust in the shared definition is persistent: once the merge is judged correct, it stays correct. It assumes trust is transferable: a new call site that fits the existing signature can adopt the definition without re-deriving why the definition says what it says. Persistence and transferability are what make the abstraction economical. A single change at the reference propagates to every caller, and no caller has to re-establish the meaning for itself.
The system was built to optimize for that propagation. The value of the abstraction is measured by how many call sites it serves from one definition, not by whether those call sites still share a requirement. Nothing in the design revalidates the original judgement. The abstraction records that the instances were once the same. It does not carry the reason they were the same, and it has no mechanism to check whether that reason survives. Trust is delegated to the reference and never called back for renewal.
What changed was not the code and not the skill of anyone touching it. What changed was the validity of the assumption that the call sites still mean the same thing. Requirements diverge. One caller acquires a condition the others do not share. A parameter is added to the shared definition to serve that one caller, then a flag, then a branch, and each addition is a small, locally reasonable accommodation of a difference the abstraction was never built to represent.
Through all of this the reference stays valid. Every call site still points at the same definition, still matches its signature, still compiles and runs. The abstraction reports success because success, to the abstraction, means the pointer resolves and the shape fits. It does not mean the shared meaning still holds. The system inherited trust from a past state in which the instances genuinely were the same, and it continues to extend that trust forward without re-evaluating it, because re-evaluation is not a behaviour it has.
This is the moment the wrong abstraction becomes structural rather than stylistic. The definition is now serving 2 or more requirements that have quietly stopped being one requirement, and it serves them from a single point that every caller still trusts by reference. The assumption that sameness of form implies sameness of meaning no longer holds. Nothing in the system noticed it stop holding. The abstraction did exactly what it was built to do, which was to resolve many things to one and never ask again whether the one still describes the many.
The failure runs entirely through expected behaviour. A call site invokes the shared definition, the runtime resolves the reference, the arguments match the signature, and the code executes. Nothing in that sequence asks whether the caller’s requirement still matches the reason the definition exists. The reference resolves. The shape fits. The call returns a value. Each of those is a success by the only measure the system applies, and the system applies no other.
Reference has taken the place of validation. A parameter added to serve one caller becomes an input available to every caller. A flag that carries meaning on one path is inert on another, yet it travels to all of them because they share the definition. The signature widens to hold requirements that have stopped overlapping, and each call site inherits the entire surface whether or not it has a use for it. What the abstraction authenticates is that the argument list conforms. It has no operation that authenticates why.
This is identity of source standing in for integrity of content. The definition is trusted because it is the thing being pointed at, not because it remains correct for the caller pointing at it. And because every step is legitimate, there is no bypass to detect. Nothing is circumvented. The wrong abstraction is not a breach of the system, it is the system executing precisely as designed against inputs whose shared meaning has already expired. The build is green. The tests that cover the shape still pass. The resolution succeeded. The system reports exactly what it was built to report, and what it reports is that the reference is intact.
The pattern is execution based on reference, not verification. A system that resolves a reference and acts on it is not confirming that the thing behind the reference is still what it was when the reference was created. It is confirming that the reference resolves. These are two different operations with two different costs, and a system built to scale performs the cheaper one at every opportunity. Verification would require re-deriving meaning at each use. Reference requires only that a pointer lands. Scale is the incentive, and the system optimized for it.
The same mechanism runs a package manager. A dependency in a Node project is named by a semantic version, a constraint such as ^1.2.0 that permits a range. The resolver selects one version, writes it into package-lock.json, and records an integrity hash of the content it found. From that point the system installs and executes that dependency because the reference resolves and the hash matches, not because the code was read again. The hash is not validation of the content. It is confirmation that the bytes equal the bytes that were present when the pin was made. It authenticates a past state, faithfully, and inherits every trust decision embedded in that state without re-examining any of them.
The correspondence is exact. The lockfile resolves the meaning of a dependency once, at the moment of pinning, and distributes that single resolution to every install that follows. The version number is a reference to a body of behaviour the resolver never inspects for purpose. When the behaviour behind that reference changes character while the reference stays valid, the system continues to fetch and execute it, reporting success, because success means the pointer landed and the hash held. This is the same structure as the shared function. Resolve many things to one, trust the one by reference, and never ask again whether the one still describes the many. The abstraction and the lockfile are the same machine pointed at different material.
A system that inherits trust from a past state has no moment at which it asks whether that state still holds. The abstraction resolves meaning once, when it is created. The lockfile resolves it once, when it is pinned. Neither is built to revalidate, because revalidation is a behaviour, and it was never one they had.
There is no attacker required for this to fail. The condition is internal to the design. Sameness of form was accepted as a proxy for sameness of meaning, and the proxy kept reporting valid long after the meaning behind it diverged. What Sandi Metz named in 2016 was not a coding mistake. It was a property of any system that concentrates trust into a reference and calls that reference authoritative.
The reference still resolves. The signature still fits. The hash still matches. And none of it means what it once meant. The system resolves the truth once and does not revalidate. The control exists. The outcome does not.
Keep Reading
systems driftPAN-OS remembers the verdict, forgets the reasoning
Firewall rules, AD groups, and JWTs keep executing stored references long after the reality they described has drifted. The system revalidates nothing.
systems driftThe log that killed your SSD was working perfectly
A Codex logging bug can write terabytes to a local SSD because the system resolves a configured reference repeatedly and never revalidates the trust behind it.
systems driftIn January 2025, a hash passed for proof
The open reproduction of DeepSeek-R1 shows verification has no place inside the systems that consume model artifacts. Adoption ran on reference alone.
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.