Why Clean Code Is Not the Same as Good Engineering
Clean code can improve local readability, but it is mostly a matter of taste and familiarity, not an objective measure of engineering quality.
"Clean code" sounds like an objective property.
It makes it sound like code can be judged the way a test can: it either passes the standard or it does not.
In practice, that is usually not what is happening.
Most of the time, when engineers call code clean, they mean it feels readable to them. And readable is shaped heavily by familiarity. It comes from the patterns you have seen before, the conventions your team prefers, and the defaults your language or framework keeps nudging you toward.
A React engineer, a backend engineer, and a systems engineer can all look at the same code and disagree on whether it is clean. Not because one of them is wrong, but because they are optimizing for different things.
Clean code is often just familiar code wearing the mask of objectivity.
That does not make it useless. It makes it contextual.
Clean code is a useful lens, not a law
I do not think the clean code crowd is wrong about everything. A lot of the advice is useful.
Meaningful names help. Avoiding unnecessary surprise helps. Keeping the important path easy to follow helps.
Those are good patterns because they usually improve local clarity.
The problem starts when patterns harden into rules.
Small functions can fragment logic.
Abstractions can hide the only path that matters.
Avoiding comments can remove the domain context that makes the code make sense.
A rule that improves one codebase can make another one worse.
That is why I think books like Clean Code are more useful as vocabulary than as law. They give teams words for discussing readability and structure. They become dangerous when every guideline gets treated like a universal truth.
Who is this code optimized for?
There is a more useful question under all of this:
Who is the code for?
Because there is not one answer.
- Humans want clear structure and visible intent.
- Teams want consistency and shared conventions.
- Tools and AI benefit from explicitness and predictable patterns.
- The machine only cares about instructions and runtime behavior.
When we call something clean, we are usually optimizing for one of those audiences whether we say it out loud or not.
And those optimizations do not always line up.
The CPU does not care about naming, indentation, or elegance. It executes instructions. That is the closest thing we have to an objective interpretation of the code.
That does not mean runtime performance is the only thing that matters. It means clean is not a property of the code in the same way behavior is.
Where clean code starts to strain
Clean code works best when the complexity is local.
One function does one thing. The control flow is straightforward. The important behavior is visible in one place.
But a lot of real systems are hard for reasons that do not live inside one file.
The complexity lives in timing, failure, and interaction:
- when things run
- what happens when they fail
- what other systems quietly depend on
- how behavior changes over time
That is where clean code stops being enough on its own.
Take concurrency.
You can write very clean async code with small functions, clear names, and tidy abstractions. But unless you are deliberate, the code still does not tell you what runs in parallel, what order matters, or where race conditions can show up. The code reads well while the execution model stays implicit.
Or error handling.
You can centralize it behind clean wrappers and keep call sites nice and tidy. That looks good in a diff. But now expected failures and unexpected ones start to blur together, retry behavior gets harder to trace, and recovery paths become less visible.
Or distributed systems.
You can wrap a remote call behind a clean interface so it looks like a local function call. But it is still a network hop with latency, retries, and partial failure. The abstraction is clean. The system is not.
That is the catch.
Clean code does not remove complexity. It decides where that complexity is visible.
Clean code depends on the primitives underneath it
Another thing that gets missed in these debates is that a lot of clean code advice assumes the language or framework gives you good primitives for expressing it.
Take dependency injection.
"Make dependencies explicit" is a reasonable principle. But without a framework or language primitive to support it, you can end up with this:
function doThing(db, logger, config, metrics, cache, queue, authClient, featureFlags) {
// ...
}Technically explicit. Also miserable to work with.
At that point, you are following the rule while making the system worse.
In a larger codebase, you usually need something better: a container, a module boundary, a context object, or a framework primitive that keeps the dependency story honest without turning every function signature into noise.
That is the broader pattern.
A guideline that works well in one environment can fall apart in another if the surrounding primitives are different.
Clean is not the same as simple
There is also a distinction I think matters more than people admit: clean and simple are not the same thing.
Clean code usually focuses on how something looks locally:
- naming
- formatting
- function size
- structural neatness
Simplicity is about whether the system is easy to understand, reason about, and change.
Those are different goals.
You can have very clean code that is hard to reason about because the important behavior is spread across too many layers of abstraction.
You can also have slightly messy code that makes the system's behavior obvious.
If I have to choose, I care more about simplicity at the system level.
Because the point is not to win a style argument. The point is to help the next engineer understand what this thing actually does.
A better standard
I still care about clean code. I just do not think it deserves the status people sometimes give it.
It is a useful lens for shaping code so humans can read it.
It is not an objective standard, and it is definitely not a guarantee that a system is understandable or correct.
When I am evaluating code, the questions I trust more are:
- Who is this optimized for?
- What important behavior is this hiding?
- Where does this run?
- What happens when it fails?
- What assumptions are no longer visible?
Clean code helps you read a file.
Good engineering makes the system understandable.
Those are related, but they are not the same problem.
Related writing
Why AI-Native Apps Break Traditional CRUD Assumptions
Even simple AI workflows create architectural pressure that traditional CRUD systems were never designed to handle.
Sometimes the Gross Solution Is the Right One
The right engineering decision is not always the cleanest one. Sometimes the safer move is to contain the mess instead of triggering a risky rewrite.
Constraints Are a Feature, Not a Limitation
Flexible APIs look powerful, but in real systems constraints often create better usability, consistency, and safer defaults.

