In software development, we chase the holy grail of “maintainable code.” We’re told to write comprehensive tests, document everything, and diagram our designs.
But what if those well-intentioned practices are actually slowing us down?
Enter the Duplication Paradox — a subtle tension where every artifact beyond the code itself (tests, docs, diagrams) duplicates the system’s intent, introducing risks of inconsistency and bloat.
This post explores how to resolve that paradox, not through shortcuts—but through clarity. Because once your code is the documentation, maintenance becomes effortless, refactoring becomes fast, and knowledge transfer becomes natural.
Understanding the Duplication Paradox
At its core, software is about behavior — what the system does when it runs. The code is the primary expression of that behavior. Everything else—unit tests, Javadoc comments, UML diagrams, user stories—is a duplication of that truth.
Why call it duplication? Because these artifacts describe or verify what the code is supposed to do. A test asserts expected outputs. A diagram maps relationships. A document explains usage.
They’re all mirrors of the same intent.
We need these mirrors to communicate and verify—but duplication comes at a cost: as code evolves (and it always does), mismatches creep in. Which one is the source of truth—the code or the test? The diagram or the implementation?
Unchecked, this drift leads to brittle systems: 1 - Outdated docs mislead new devs. 2 - Tests fail for the wrong reasons. 3 - Over-documented designs resist change
The result? Maintenance hell, where fixing one thing breaks three others.
💡 The key insight: minimize necessary duplications by making the code so intuitive that extras become optional.
The Core Approach: Simplicity Through Domain Focus
This isn’t laziness—it’s ruthless efficiency. Treat the code as the single source of truth. Here’s what that looks like in practice.
Embrace Domain-Centric Design
Boil your system down to 4–8 core domain objects. These form the backbone of the application.
Example: In a KYC system, everything revolves around Person and Risk. Supporting types like SanctionsCheck or TransactionPattern exist solely to compute or influence Risk for a Person.
Why this works: The domain model is the design. Read the code and the structure reveals itself. There’s no need for separate UML diagrams or long architecture docs.
Knowledge transfer bonus: Onboard a new dev? You simply say, “Follow the Risk flow.” Whiteboard sessions during design suffice; persistent artifacts rarely do.
Skip Superfluous Documentation
There’s a world of difference between building an API or library and building an application.
When you publish a library, documentation is essential. Consumers don’t see the implementation—they rely on clear contracts. A well-written Javadoc or README is a communication interface, helping others understand how to use your code safely.
But an application is different. You’re not consuming it—you’re developing it. You must already understand the code you’re working with, because you’re the one changing it. Adding Javadoc here doesn’t clarify—it duplicates what’s already obvious from reading the source.
And that duplication has side effects:
It quickly drifts out of sync.
It hides real intent behind stale prose.
It gives a false sense of “documentation coverage” while reducing code legibility.
In an application, the best documentation is the code itself. If something isn’t clear enough without a comment, the code—not the comment—needs fixing.
So instead of writing Javadoc, write self-explanatory code:
Use precise, domain-oriented names.
Keep methods small and cohesive.
Let class and method structure communicate purpose directly.
Javadoc in an application doesn’t improve understanding—it introduces an unused duplicate that obfuscates it.
Avoid the Test Coverage Trap
Don’t chase 100% coverage—it’s a false idol. When your code is “Mickey Mouse” simple, 100% tests add risk, not safety.
Here’s why:
False confidence: Developers stop thinking critically once “the tests are green.”
Predictable scenarios: Tests only cover what you foresee; real bugs live in what you don’t.
Maintenance drag: Every small refactor means updating fragile tests that mirror implementation, not behavior.
So instead of testing everything, test what matters. Aim for 30–45% coverage, focusing on the risky or complex paths—the parts where human reasoning can fail.
Simplicity is the best QA strategy:
When logic is obvious, bugs are rare.
When names are precise, intent is unambiguous.
When context is embedded in the class, refactoring is safe and fast.
In my experience, less than 10% of total time goes into bugs or refactorings. There’s virtually no tech debt, because clarity and domain focus prevent it from forming.
Leverage Object Orientation for Contextual Clarity
Objects carry their context. A Person knows its own logic; a Risk knows how to evaluate itself. That context eliminates ambiguity—every method exists where it belongs.
This design makes refactoring almost trivial: Change flows naturally through cohesive boundaries. You don’t “hunt” behavior across layers—it’s already grouped by meaning.
The result? Refactorability as a superpower. When intent is obvious, change is quick, safe, and predictable.
The Payoff: Maintenance Mastery and Knowledge Velocity
This approach flips traditional wisdom, yielding outcomes most teams dream of:
Ultra-Low Maintenance: Few duplications → few inconsistencies. Code reads like a story; debugging feels like reading comprehension, not archaeology.
Lightning-Fast Refactoring: Simple structures and OO context mean local, isolated changes. No cascade of broken tests or outdated diagrams.
Effortless Knowledge Transfer: New developers ramp up by following the domain, not the documentation. “Here’s the core flow” replaces “here’s the 40-page onboarding guide.”
Bug Resilience: High coverage doesn’t guarantee safety—clarity does. Real bugs surface fast, are shallow, and get fixed immediately.
Critics might call this “undocumented chaos.” But in practice, it’s the opposite—clarity replaces documentation. You don’t skip best practices; you make them redundant through stronger fundamentals.
When to Adapt
This philosophy isn’t dogma—it’s context-aware minimalism.
Building libraries or public APIs? Add Javadoc—users need the manual.
Working in regulated domains? Increase test coverage for compliance.
Facing distributed complexity? Add targeted architecture diagrams to align understanding.
The rule of thumb: If your code feels “Mickey Mouse” (simple, almost boring), you’re winning. If it feels like a puzzle, simplify before scaling.
Conclusion: Resolve the Paradox, Unlock Flow
The Duplication Paradox reminds us: more isn’t better. Every extra layer—test, doc, diagram—duplicates intent and adds fragility. By focusing on clarity and domain logic, we dissolve complexity at the source.
In an era of AI-assisted coding and rapid iteration, this clarity-first minimalism is more relevant than ever. It’s the quiet superpower that makes teams move faster, communicate effortlessly, and maintain code that lasts.
Next time you’re tempted to add “just one more test” or diagram, ask: Does this duplicate unnecessarily? Often, the answer is yes—and the code can stand alone.
Master this, and your software won’t just work—it’ll thrive.
Top comments (0)