Clean Code: The Cult of Dogma and Why Your Abstractions Are Probably Wrong
TL;DR
Clean Code isn’t the programming bible we thought it was. While it introduced valuable concepts like meaningful names, the DRY principle, and the boy scout rule, its dogmatic insistence on 2-4 line functions, zealous abstraction, and elimination of all duplication has led a generation of developers astray. The book’s own example code violates its own advice, filled with side effects, breaking command-query separation, and creating incomprehensible abstractions. Worse, it turned “clean code” into an identity badge rather than a tool for reasoning about trade-offs. If you’re learning software design today, read A Philosophy of Software Design or The Pragmatic Programmer instead. They teach you to think, not to follow mantras.
What Clean Code Got Right
Let’s give credit where it’s due. Robert C. Martin’s 2008 book accomplished something remarkable: it made an entire generation of programmers care about code quality. Before Clean Code, “move fast and break things” meant shipping garbage and forgetting about it. After Clean Code, we had a vocabulary for discussing maintainability.
The book’s lasting contributions:
The only way to go fast is to go well. This single insight, that technical debt compounds faster than you think, saved countless projects from death marches.
Foundational naming advice that holds up perfectly:
- Variables and functions should reveal intent
- Use problem-domain names (the DDD “ubiquitous language” before we called it that)
- Make names searchable,
WORK_DAYS_PER_WEEKbeats5 - Method names should be verb phrases:
postPayment,deletePage,save
The DRY principle (Don’t Repeat Yourself), though we’ll see how this became a weapon of mass abstraction later.
Command-Query Separation: methods should either do something (command) or answer something (query), not both. When a method named getStatus() also sends emails, developers rightfully lose their minds.
The Boy Scout Rule: leave code cleaner than you found it. Small, incremental improvements beat big-bang rewrites.
Exception handling best practices: use unchecked exceptions, provide context, don’t return null.
Pure functions without side effects should be preferred. Stateless functions are easier to test, reason about, and parallelize.
The FIRST principle for unit tests: Fast, Independent, Repeatable, Self-validating, Timely. Still the gold standard.
The comprehensive code smells catalog remains valuable, every team should know what “shotgun surgery,” “feature envy,” and “primitive obsession” mean.
For junior developers especially, these fundamentals are timeless. I still find myself repeating this advice in code reviews fifteen years later.
The Obsolete Parts
Some of Clean Code’s content simply aged poorly:
Heavy Java/OOP focus: The book uses Java EJBs and AspectJ extensively. In 2025, with TypeScript, Rust, Go, and functional paradigms dominating greenfield projects, the pure OOP approach feels dated.
Outdated tooling: Entire chapters on code formatting and style can now be replaced with: “Run prettier and never think about this again.”
These limitations are forgivable. Every programming book dates poorly. What’s less forgivable is what comes next.
The Dangerous Dogma
Here’s where Clean Code becomes actively harmful. The book presents rules as absolute laws, delivered with zero nuance about trade-offs:
The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.
Functions should be two to four lines long.
Functions should not be indented more than two levels.
An ideal function has zero arguments (but still no side effects?).
This isn’t advice. It’s fundamentalism. And it’s backed not by empirical research but by anecdote:
“When Kent Beck showed me the code, I was struck by how small all the functions were… Each was just two, or three, or four lines long.”
That’s it. That’s the evidence. Kent Beck wrote tiny functions once, therefore you must too.
This mantra-driven approach created a generation of clean code zealots who attached their professional identity to metrics rather than outcomes. If you could count lines of code and duplication, you could feel good about yourself without understanding whether the code was actually better.
When Abstraction Becomes Self-Parody
Consider this real scenario from Dan Abramov’s essay “Goodbye, Clean Code”:
A developer writes code with some duplication, methods for resizing rectangles, ovals, and text boxes by dragging handles. The code works. It’s about 200 lines.
A zealous colleague stays late, removes all duplication, and produces an elegant 100-line abstraction with Directions and Shapes composed together. Beautiful! Clean!
The next morning, their boss asks them to revert it.
Why? Because the abstraction traded changeability for eliminating duplication. When new requirements arrived, different behaviors for different handles on different shapes, the original “messy” code stayed easy to modify. The “clean” abstraction would require tortured conditional logic and parameter passing to accommodate reality.
The kicker: the refactorer never talked to the original author. They just imposed their abstraction without discussing requirements. This destroyed team trust and created a maintenance nightmare.
The lesson: duplication is far cheaper than the wrong abstraction.
Sandi Metz and The Wrong Abstraction
Sandi Metz crystalized this pattern in her essay “The Wrong Abstraction”:
- Programmer A sees duplication
- Programmer A extracts it into an abstraction
- New requirement appears that’s almost the same
- Programmer B adds a parameter and conditional logic
- Another requirement. Another parameter. More conditionals.
- Loop until incomprehensible
The code is now a condition-laden procedure interleaving vaguely associated ideas. It’s hard to understand and easy to break. But the sunk cost fallacy kicks in: “This must have taken ages to get right. It would be a sin to let that effort go to waste.”
Metz’s advice when you find yourself here:
- Inline everything, put the abstraction’s code back in every caller
- Delete unused code for each specific caller
- Start fresh with current requirements
This is advance, not retreat. The fastest way forward is back.
The Example Code Is Terrible
The most damning evidence against Clean Code is the book’s own example code. After spending chapters telling you how to write functions, Martin presents this as the ideal:
public class SetupTeardownIncluder {
private PageData pageData;
private boolean isSuite;
private WikiPage testPage;
private StringBuffer newPageContent;
private PageCrawler pageCrawler;
private String render(boolean isSuite) throws Exception {
this.isSuite = isSuite;
if (isTestPage())
includeSetupAndTeardownPages();
return pageData.getHtml();
}
private boolean isTestPage() throws Exception {
return pageData.hasAttribute("Test");
}
private void includeSetupPages() throws Exception {
if (isSuite)
includeSuiteSetupPage();
includeSetupPage();
}
// ... 13 more tiny private methods
}
This is Martin’s own code, written to his standards, as a teaching example. Let’s count the problems:
Side effects everywhere: Nearly every method modifies member variables (this.isSuite, newPageContent) or calls methods that do. Earlier in the chapter, Martin wrote:
“Side effects are lies. Your function promises to do one thing, but it also does other hidden things… they are devious and damaging mistruths.”
So why does his “clean” code do nothing but this?
Breaking Command-Query Separation: render() sets this.isSuite (command), then calls isTestPage() (query), then returns pageData.getHtml() without ever touching pageData. Where did the HTML come from? Was it already there? Did includeSetupAndTeardownPages() modify it? You can’t know without reading every method.
Impossible to test: You can’t unit-test render() in isolation. It’s not a unit, it’s coupled to half the class through side effects.
Pointless extractions:
private boolean isTestPage() throws Exception {
return pageData.hasAttribute("Test");
}
This “abstraction” isn’t meaningful, it’s a literal restatement of its implementation. It’s called from exactly one place. Why does this method exist?
Method names that lie: What does wayTooCold() do? It sounds like a query (is it way too cold?) but it’s actually a command (set temperature to way too cold AND call controller.tic()). The book explicitly says methods should be verb phrases, then violates this in its own examples.
The prime number generator example is even worse, fifteen microscopic methods with names like smallestOddNthMultipleNotLessThanCandidate() and isLeastRelevantMultipleOfNextLargerPrimeFactor(). The function names are harder to understand than the code they replaced.
The Unit Testing Chapter’s Self-Contradiction
Clean Code advocates for clear, simple tests. Then it shows this transformation:
Before (too much detail):
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
hw.setTemp(WAY_TOO_COLD);
controller.tic();
assertTrue(hw.heaterState());
assertTrue(hw.blowerState());
assertFalse(hw.coolerState());
assertFalse(hw.hiTempAlarm());
assertTrue(hw.loTempAlarm());
}
After (clean!):
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
wayTooCold();
assertEquals("HBchL", hw.getState());
}
This is presented as an improvement, inventing a “domain-specific testing language.”
It’s catastrophically worse. The original test was transparent: set temperature, tick the controller, assert five observable states. The “clean” version:
- Hides
controller.tic()insidewayTooCold()(side effect!) - Uses a magic string
"HBchL"that requires decoding - Makes the test completely opaque to anyone who doesn’t have the DSL documentation memorized
The lesson here was supposed to be about DSLs. The actual lesson: explicit is better than clever.
The Three-Phase TDD Loop Nobody Can Actually Follow
Clean Code presents Test-Driven Development as:
- Write a failing test
- Write just enough production code to pass
- Refactor
- Repeat in 30-second cycles
This “locks you into a cycle that is perhaps thirty seconds long.”
But there’s a missing step zero: figure out how to decompose your problem into 30-second increments. For non-trivial problems, this is often harder than solving the problem itself, and frequently impossible.
I’ve watched teams try to follow this dogmatically and end up paralyzed, spending hours decomposing before writing a single line. Or they fake it, writing tests after the code just to appease the process.
TDD is a tool. Sometimes it’s the right tool. Often it’s not. The book presents it as the One True Way.
What “Data Structure” Actually Means
In Chapter 7, Martin redefines “data structure”:
Data structures expose their data and have no meaningful functions.
He then contrasts this with “objects” which hide data and expose behavior.
Fine, but this is not what the rest of the world means by “data structure.” We mean arrays, linked lists, hash maps, trees, graphs, stacks, queues. The chapter has zero content about these fundamental computer science concepts and how to write clean code using them.
This is a stunning omission for a book claiming to teach software design.
So What Do We Do Instead?
1. Think in trade-offs, not rules
Every design decision has costs and benefits. Extracting a function improves readability but adds indirection. DRY reduces duplication but increases coupling. Understand the trade-offs and make conscious choices.
2. Embrace duplication when it reveals structure
As Sandi Metz argues, sometimes you need duplication to see the real abstraction. Don’t abstract on the first occurrence. Wait for the third.
3. Optimize for change, not cleanliness
Code that’s easy to modify beats code that’s aesthetically perfect. A little messiness is fine if it makes future requirements easier to accommodate.
4. Use your judgment
Kent Beck’s most important quote: “It depends.”
Context matters. Team experience matters. Project constraints matter. Don’t let a book from 2008 override your understanding of your specific situation.
5. Read better books
If you’re learning software design today:
- A Philosophy of Software Design (John Ousterhout, 2018) , focuses on managing complexity at the right level of abstraction
- The Pragmatic Programmer (Hunt & Thomas) , teaches practical wisdom, not dogma
- Code That Fits in Your Head (Mark Seemann) , modern take on cognitive load and design
- 99 Bottles of OOP (Sandi Metz) , teaches you to recognize when to abstract and when to wait
These books teach you to think instead of follow mantras.
The Continuous Clean Code Trap
The greatest harm Clean Code inflicted wasn’t bad example code, it was turning “clean code” into an identity.
“I’m the kind of person who writes clean code” becomes a self-deception. You measure lines per function, count duplication, enforce strict lint rules, and feel righteous. Meanwhile, your abstractions are wrong, your tests are brittle, and your colleagues can’t understand your code.
This is phase many of us went through. When we don’t feel confident, we attach our self-worth to measurable metrics. Removing duplication feels like improving objective quality. It messes with our sense of identity.
The cure: let clean code guide you, then let it go.
Code cleanliness is not a goal. It’s a defense mechanism when you’re not sure how a change will affect the codebase. It’s training wheels. Useful for beginners, limiting for experts.
Final Verdict
Should you read Clean Code in 2025? Only with heavy caveats.
Skip it if: You’re a beginner looking for your first software engineering book. The dogma will confuse you more than it helps.
Read it if: You’re experienced enough to disagree with half of it and want to understand the cultural moment that shaped a generation of developers.
Definitely skip: Treating it as scripture. The book itself admits “many recommendations are controversial” and “we can’t claim final authority.” Take that seriously.
The book’s greatest gift wasn’t its rules, it was starting a conversation about code quality. That conversation has moved on. It’s time we moved on too.
Recommended reading order for learning software design in 2025:
- The Pragmatic Programmer (timeless practical wisdom)
- A Philosophy of Software Design (managing complexity)
- 99 Bottles of OOP (when to abstract)
- Clean Code (with critical eye, as historical artifact)
- Domain-Driven Design (if tackling complex domains)
References:
https://qntm.org/clean https://gerlacdt.github.io/blog/posts/clean_code/
Continue reading
Next article
FastAPI in Production - Full Guide
Related Content
Hexagonal Architecture: Why Your Domain Logic Shouldn't Know About Your Database
Stop letting frameworks dictate your architecture. Learn how Hexagonal Architecture (Ports & Adapters) isolates business logic from infrastructure, makes testing trivial, and lets you swap databases without rewriting code.
S (Single Responsibility) from SOLID
Understand the Single Responsibility Principle from SOLID. Learn why a class should have only one reason to change and how SRP leads to more maintainable, testable code.
TLS: How Your Browser Keeps Secrets (And Why It's Harder Than You Think)
A no-bullshit deep dive into TLS 1.3: the handshake, record protocol, certificate chains, and why perfect forward secrecy actually matters. With annotated diagrams because the RFCs are 100+ pages.