In the Age of AI-Generated Code, Why Sandi Metz Is Surging Again

Software EngineeringAbstractionDRYCode QualityAI Programming

Sources:HN + Lobsters · HN

It’s 2 a.m. You’re staring at a diff line: if is_premium and not is_trial and billing_cycle == 'annual'. Your cursor hovers over “Request Changes,” refusing to click. The Pull Request is titled “Merge customer and broker discount calculation logic.” Two blocks of code really do look almost identical — load a record, update a percentage field, write back to the database. One engineer spotted this “duplication” and extracted a unified method with an entity_type parameter. It looks clean, reasonable, DRY.

But you know the customer discount is about to switch to tiered pricing tomorrow, while the broker commission logic won’t change for two years. Forcing them together now superficially eliminates duplication, but in practice welds two concepts with entirely different evolutionary trajectories into a single seam. This is the trap Sandi Metz warned about a decade ago. In June 2026, her article resurfaced at the top of Hacker News with 409 points and 272 comments — in an age when AI can generate five hundred lines of “looks correct” code in a single shot, this principle demands reexamination more urgently than ever.

Sandi Metz Drew a Map of Rot

Metz first said “duplication is far cheaper than the wrong abstraction” in her 2014 RailsConf talk, then wrote it up as a blog post in 2016. Her argument is strikingly simple, relying on no theoretical framework — she merely described a degeneration process everyone has experienced but few have named:

Programmer A spots duplicated code. They extract a shared method or class, replace all the duplication sites, and walk away satisfied. Time passes. New requirements arrive. The existing abstraction is almost sufficient. Programmer B takes over and, out of respect for existing code, doesn’t start from scratch — they add a parameter to the method, then add a conditional branch inside it. Then a third requirement. A fourth parameter. A fifth if-else. By step eight, you show up, staring at thousands of lines of tangled conditional logic, trying to understand which branches belong to which caller.

Metz’s solution is equally simple: inline the abstraction back, let each caller keep only the code it genuinely needs, then observe from scratch — which similarities are real, and which merely “look alike.”

This passage has penetrating power because it breaks a near-religious belief among programmers: that duplication is evil, and eliminating duplication is axiomatically correct.

DRY’s Historical Baggage: A Mistranslation from Databases to Codebases

The DRY principle was introduced by Andy Hunt and Dave Thomas in the 1999 book The Pragmatic Programmer. The original formulation: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” The emphasis is on knowledge, not on characters. A SQL query, a business rule, a configuration value — these are knowledge. Two accidentally similar for-loops probably aren’t.

But the industry progressively compressed this distinction in transmission. “Don’t Repeat Yourself” became “don’t have repeated lines of code.” A heuristic elevated to a hard rule spawned a vast number of abstractions that should never have existed: generic Repository base classes, universal Processor methods, service functions with parameter lists longer than their business logic.

What Metz is doing is essentially recalibrating DRY: she’s pushing back against premature DRY. This point was echoed repeatedly in the HN discussion — “the article isn’t saying don’t abstract; it’s saying don’t force abstraction.”

Engineering Signals That an Abstraction Is Wrong

In the HN comments, multiple engineers shared their heuristics for recognizing wrong abstractions. “Is this code doing the same thing, or does it just look the same?” — this was the most frequently cited core criterion. When the following signals appear together, the abstraction is probably wrong:

Parameter-driven conditional branching. A method takes boolean or enum parameters and uses if-else internally to dispatch to code paths that barely overlap. Each new parameter multiplies the state space the caller must understand.

Changing one caller’s behavior requires writing “catch-all” logic for the others. This means the callers don’t share a genuine co-variation relationship. They just happen to be running similar code today.

The abstraction has no self-evident reason to exist. A healthy abstraction can be understood without consulting its callers. If every time you read it you have to trace back through three call sites’ context to figure out what this logic is doing, the abstraction has already lost its greatest value — reducing cognitive load.

When adding a new feature, your first instinct is to bypass the abstraction, not reuse it. This is the most reliable signal. Human intuition often catches structural problems earlier than rationalized post-hoc explanation.

HN’s Core Debate: Single Source of Truth vs. Locality

In this HN discussion, two highly-upvoted comments precisely delineated the boundaries of the dispute.

User lg5689’s position represents the core reasoning of the “abstraction-first” camp: “One should always follow the single source of truth principle. If two duplicated code locations would constitute a bug when they diverge, you should refactor. Duplication creates invisible long-distance coupling in code.” This logic comes from a clean engineering intuition: when the same business rule is distributed across two locations, the day someone changes one and forgets the other, a bug is planted.

User jonahx’s response points to the scenario Metz is really concerned with: “Fundamentally, the article is discussing the situation where you don’t yet know how many sources of truth there are. Are these two locations using the same algorithm, or slightly different versions? More importantly, will they change for the same kinds of reasons? Most critically, the wrong abstraction destroys locality — which is actually the only property you truly care about when modifying code. I just want to make this one change without worrying about side effects on unrelated parts of the system.”

Both comments have merit, but apply to different scenarios. If you know two locations represent the same invariant fact — the same tax rate, the same encryption algorithm, the same data validation rule — then abstraction is the correct choice, and the benefit of a single source of truth far exceeds the cost of the abstraction itself.

The problem is that in this industry, we overestimate our ability to “see whether two blocks of code are synonymous.” Metz’s example: two calculations look similar — load a customer record, update a discount percentage; load a broker record, update a commission percentage. They happen to both follow a “load-entity-update-percentage” pattern today. But customer discount business logic could switch to tiered calculation at any moment, while broker commission remains a flat percentage — because the nature of these two fields is entirely different in legal, contractual, and accounting terms.

The line between “code that looks the same” and “code that represents the same truth” is harder to draw accurately than most engineers are willing to admit.

How AI Code Generation Amplifies This Problem

This is precisely why Metz’s article was pushed back to the top as AI coding tools reached mass adoption.

LLMs have two structural tendencies in code generation. First, they naturally incline toward “eliminating apparent duplication.” When you use a single prompt to generate two similar functional modules, the model extracts the most “standard” merging approach from its training data and produces an abstraction with parameters. It doesn’t ask you what the business boundary between customer and broker is — it wasn’t in the requirements meeting. It’s merely finding the statistically optimal shared representation.

Second, more subtly and more dangerously, LLM-generated wrong abstractions are anomalously smooth. Naming is sensible, indentation is correct, parameter arrangement has logical flow. A wrong abstraction written by a human engineer often has a smell — the naming is awkward, the structure is loose, you can feel it straining to fit. An LLM’s wrong abstraction looks professional, confident, unimpeachable. Reviewers are more likely to let it through.

Multiple commenters in the HN discussion pointed out this tension. One said “LLMs are natural anti-abstraction machines” because they don’t understand business semantics — they only understand surface patterns. Another said “LLMs have dramatically lowered the cost of copying, so abstraction now requires a much higher bar of justification.” A sharper observation: “What I spend the most time thinking about is how to explain to an LLM how an existing codebase actually works, without it distorting it through misunderstanding.”

An interesting engineering phenomenon: large volumes of AI-generated code tend toward duplication rather than abstraction. The reason isn’t that the model understands Metz’s principle — it lacks persistent cross-file memory between successive requests. It doesn’t know it wrote something similar in a previous session — unless you stuff the related code into the context window. As a result, AI artifacts contain large numbers of “blocks that look like they should be abstracted but weren’t,” alongside “blocks that were abstracted but entirely in the wrong direction.” Both error types concentrated in the same repository — this may be the new daily reality AI-assisted programming delivers to maintainers.

Choosing Between Two Errors

Metz’s position is often oversimplified to “duplication is better than abstraction.” That’s not quite fair. What she actually means is: if you must choose between duplication and the wrong abstraction, choose duplication. This is a second-order principle — it doesn’t tell you what’s right; it tells you which direction’s error is cheaper when you’re not sure what’s right.

A highly-upvoted HN comment offered a practical operating rule — the “Rule of Three”: first occurrence, write it down. Second occurrence, tolerate the duplication but start observing. Third occurrence, consider abstraction — and only along the axis that’s genuinely changing. This rule embeds a key prerequisite: you need time for the genuine pattern to emerge. Only after code has lived in the repository for a while does it become discernible which call sites will change in sync and which will diverge.

Another commenter’s summary was sharper: “The opposite of DRY isn’t duplication; it’s WET — Write Everything Twice. Write it twice, then observe. Write it three times before you act.”

Engineering Judgment After the Data

The HN voting data — 409 points, 272 comments — shows this touched a fracture in the engineering community that hasn’t yet healed. Everyone knows DRY can be misapplied. The problem is that generation after generation of new engineers still receive an education at onboarding that treats “eliminate duplication” as a non-negotiable priority in code review.

In an age where AI can write compliant code for you, the genuinely scarce skill is no longer “how to abstract” but “when to abstract.” The latter requires not technique but patience, judgment formed through sustained observation of a business domain, and the composure to dismantle an abstraction in the face of sunk cost. Metz’s original words echo into today: “When faced with the wrong abstraction, the fastest way forward is backward.”

There is no ultimate answer here. I hold no absolute position on either side of this debate. Abstraction is one of the few genuinely foundational concepts in software engineering, but its value is highly dependent on timing and context. This article does not advocate replacing abstraction with duplication. What it aims to point out is a narrower judgment: in the new normal of code produced alternately by humans and machines, the cost of “wait a bit before abstracting” may be lower than we’ve long believed — and the cost of “abstracted wrong, now dismantle it” may be higher than we anticipated.


Author’s note: This article is based on analysis of Sandi Metz’s original post, the June 2026 HN discussion, and related engineering literature. It does not constitute absolute technical advice. Engineering decisions require integration with specific context — team size, business stage, codebase age, test coverage — any one of these variables could flip the direction of this article’s judgments.