Programming Principles in the Age of AI

Programming Principles in the Age of AI: DRY

·7 min read

This is the first article in a series of thought experiments where I revisit programming principles through the lens of AI-assisted development. Obviously, we are in the midst of a paradigm change yet nobody is sure where it leads. Thus, I'm not attempting to predict the future, rather I'm trying to stress-test assumptions against strong scenarios to see what holds up. Throughout the whole series we base our judgment on a more likely prediction that AI will be used as a tool, not as a sentient coworker or a utopian singularity-born God.


DRY in 30 Seconds

DRY (Don't Repeat Yourself) comes from The Pragmatic Programmer (Hunt & Thomas, 1999). In practice people use it as an “extract a method” pattern: if code duplication is encountered, it should be extracted to a common place which would be reused. A lot of people would rhyme this to Uncle Bob’s vision of single responsibility in the Single Responsibility principle, and an attentive reader of Clean Architecture would find a contradiction; but we will get to this in one of the future articles. The original definition of DRY is broader than most people think: "every piece of knowledge must have a single, unambiguous, authoritative representation within a system." For the sake of this article let’s underline that the core of the principle is about knowledge duplication, not code duplication. Code duplication is a consequence of knowledge duplication, not a reason.

The Real Reason DRY Works

DRY solves a specific problem: humans are unreliable at tracking duplicates. You have the same tax calculation in three services. Requirements change. You update two of them. The third one silently stays wrong until some customer files a complaint four months later. The abstraction is a workaround for the fact that you, the developer, will forget where the copies are.

In other words, DRY is a context management strategy. A human developer operates within a limited mental context: what you can remember, what's on your screen, what you reviewed recently. DRY reduces the amount of context you need to hold by guaranteeing that each piece of knowledge lives in exactly one place. You don't need to remember where the copies are if there are no copies.

This framing matters because it reframes the question we're trying to answer. The question isn't "is duplication bad?" (it is). The question is: "does the entity maintaining the code need DRY to manage its context?" And that depends entirely on how much context that entity has.

If AI Was Perfect...

Suppose your development environment has AI tooling capable of building a semantic index of the codebase, mapping not just syntactically identical code but functionally equivalent code. When you change one instance, it identifies all related instances and generates context-appropriate diffs. It opens PRs for each affected service with changes adapted to that service's specific constraints.

In this world, the email example plays out differently. The AI identifies all three validators, understands they've intentionally diverged on plus-addressing but share the same domain validation logic, and generates three different patches that update the domain check without touching the plus-addressing behavior. Each service keeps its own validator. No shared library. No version coupling. No deployment dependency. But consistency on the parts that should be consistent.

At first glance, this looks like it makes DRY less necessary. If an AI can track and synchronize duplicates across the codebase, why bother extracting shared abstractions? Let each service have its own copy and let the tooling handle consistency. You get local readability, flat dependency graphs, independent deployments, and the AI verifies consistency at CI time.

It sounds compelling. But there's a structural problem with this argument.

The Context Problem

The same AI capability that enables duplicate tracking also accelerates code generation. If an AI can understand your codebase well enough to synchronize duplicates, it can also generate new features faster. Developers ship more code in less time. Boilerplate that nobody would have written manually gets generated because the marginal cost is near zero. The codebase grows.

And here's where the argument becomes circular: the growing codebase eventually exceeds the AI's context window. The very tool that was supposed to track your duplicates can no longer see all of them at once.

This isn't a theoretical concern. Context windows in current LLMs are measured in tens or hundreds of thousands of tokens. A serious microservice architecture can have millions of lines across dozens of repositories. Even with RAG, embeddings, and semantic search, there's a hard limit on how much an AI can reason about simultaneously. Every technique we have for extending effective context comes with tradeoffs in precision and reliability.

The uncomfortable realization is that the argument against DRY is self-defeating: the force that weakens DRY (better AI, more context) simultaneously creates the conditions that strengthen it (more code, larger codebases, context overflow).

A Race Condition

Zooming out, the question becomes: does the context window grow faster than the codebase?

The historical trend here is not ambiguous. Codebases have been getting bigger for decades. Every tool that improved developer productivity (IDEs, frameworks, package managers, CI/CD, and now AI-assisted development) resulted in more code, not less. The industry has never responded to productivity gains by writing less. We write more, faster, aimed at more ambitious goals.

Context windows are growing too: from 4K tokens to 32K to 128K to over a million. But this is a familiar pattern in computing. We've watched the same race play out between RAM and application memory usage, between CPU speed and software complexity, between network bandwidth and data volume. The resource grows, but demand grows faster. Parkinson's Law, applied to software: code expands to fill the context available for processing it.

If this pattern holds (and there is no strong reason to believe it won't), then AI will always be operating near the boundary of what it can fully comprehend. It will be significantly better at finding and tracking duplicates than a human, certainly. But it won't have the luxury of omniscience. DRY, understood as a strategy for keeping a codebase manageable within finite context, remains relevant regardless of whether the context belongs to a human or a machine.

What Actually Changes

None of this means AI has no effect on how we practice DRY. The impact is real; it's just more nuanced than "DRY is dead."

The threshold moves. AI can track more duplicates than a human, so the point at which duplication becomes dangerous shifts further out. Small utility duplications (string formatting, basic validation) might not be worth extracting if AI tooling can verify their consistency. But large-scale knowledge duplication, shared business rules, regulatory logic, core domain concepts, still benefits from explicit shared implementations.

Verification becomes possible. Even when you choose to duplicate, AI can tell you "these five methods implement the same business rule and two of them have drifted." That's genuinely new. You can make informed decisions about duplication rather than discovering inconsistencies through production bugs. The principle shifts from "never duplicate" to "duplicate consciously, with visibility."

The distinction between code duplication and knowledge duplication, which Hunt and Thomas drew in the original formulation but which the industry largely ignored in practice, becomes operationally meaningful. AI tooling can help enforce DRY at the knowledge level while tolerating duplication at the code level, which is arguably what the principle was always supposed to mean.

Practical Takeaway

Next time you're about to extract a shared method or create a shared library, it's worth asking: am I extracting this because these things represent genuinely the same business concept, or because the code happens to look similar right now? And separately: am I extracting this because the abstraction makes the design genuinely better, or because I need a mechanism to keep things in sync within my limited context?

The first question has always been the right one to ask. The second question is where AI changes the calculus, gradually, and only within the bounds of what the tooling can actually see. For now, DRY remains the pragmatic default. Not because it's a sacred principle, but because context, whether human or artificial, is always finite. And as long as it's finite, we need strategies for working within its limits.