Discussion between Casey (author of this article) and Uncle Bob (author of _Clean Code_, whose programming patterns Casey is critiquing), posted on HN on 2023-03-11 (https://news.ycombinator.com/item?id=35105528), 223 points, 213 comments
I consider Clean Code to be in the category of books/styles that is helpful for early developers who need some structure, but harmful to late-stage developers who adopt it as dogma.
On a long enough career path, eventually you will run into one Clean Code zealot who carries an air of superiority and nit picks every PR over things like a function having more than an arbitrary number of lines in it instead of reviewing the actual code. This is the point where most people come to hate Clean Code.
I learned Clean Code at the beginning of my career, but I don't actually get it. Recently I know about "testable code" from Justin Searls. I found the "testable code" concept is more useful because we can monitor the effectiveness of the concept and I can see the actual benefits in my projects.
The function size is one rule from Clean Code I disagree with, it's silly. I love helper methods, but use them to a reasonable standard. I'd argue, if you cannot see it all on a 1080p monitor, that it might be getting a bit too long. I read PEP-8 religiously before I learned about "Clean Code" and it helped me to have sane standards in general. Methods that are roughly under 100 lines of code are okay, better is to fit it all in your monitor, 1080p being probably the most common resolution that leaves you with roughly 50 to 60 lines of code. If you have to scroll, you might want to consider helper functions to simplify and shorten logic.
Functions always being under 10 lines just means you've got functions everywhere, which can be mentally exhausting to follow logic, if you aim for like 40 lines tops you can write better "stories" with your code that are easier to follow and more expressive.
> I consider Clean Code to be in the category of books/styles that is helpful for early developers who need some structure
Clean Code is unhelpful to beginners too, though: misuse of industry-standard terms, shunning of comments in favor of tiny functions with long names, shunning function arguments in favor of mutating state, polymorphism obsession, etc. So much of the concrete advice the book gives is just plain bad.
The reason people get more pissed off at Clean Code than they would at any other book that gives bad advice is the preachy and authoritative tone it uses. It frames people who don't do "Clean Code" as unprofessional and lazy, and this framing is very convincing to some people, as evidenced by some of the replies in this thread.
> It frames people who don't do "Clean Code" as unprofessional and lazy
Well, isn't that the whole reason for the profession? If you want to be professional and hardworking, why are you instructing a computer to do your work for you? If you aren't being unprofessional and lazy, you're in the wrong business.
I would charitably say that it’s a function of a different time and place in programming. The way you write “Clean COBOL,” “Clean Java 6/J2EE,” and “Clean Rust” are all going to be very different.
There is still good meat-on-the-bone in the book, and especially interesting if you’re working in a legacy system from that older time and place.
I basically spent 2 full years removing normalization from our two projects that was originally added because devs followed best practice.
Those 2 years were to do the data migration and of course there were different priorities, if that would be priority we would do it in 1 month, but we liked to spread risk over time.
Reason was of course performance issues and not really needed joins, where normalization didn't bring any benefit.
> but harmful to late-stage developers who adopt it as dogma.
Even Robert Martin, very often in his videos and blogs, espouses "engineering judgment" and is quite fine abandoning advice in his book when the situation calls for it.
If performance is important and clean code is impacting it, he won't object to your breaking the rules.
It's mostly with his TDD evangelism that he goes (a little) crazy.
I think you’ve missed the point of clean code if this is your gripe with it.
Every time you over scope a function signature, because you want to handle that other case, you add mental tax to the next person. This accumulates, burns time, and now confuses agents, which is time and tokens ($).
No one wants to work with a dogmatic individual but I’d rather a nit picker than a human or agent slop machine.
i'd argue it is even more harmful for beginns, i remember we had to code a chess program and seeing students getting stuck on OOP stuff rather than actually solve the problems.
Sometimes it feels like the more someone has memorized all the little acronyms and jargon and principals around programming the worse they are as an engineer.
I worked with a guy who would constantly drop niche jargon and quotes from famous engineers and then kind of smugly look at you. He could not write a function without proudly saying what principals it was following. He was a horrible programmer, ended up getting laid off.
Which would be fine if these people weren't the ones making / influencing the hiring decisions, paying them half a million dollars (even though they have done nothing) and people retweeted their “opinionated” takes on subjects they barely understand.
Yes, a toy problem only needs a simple implementation. This is a straw man. And I don't even like Robert Martin's Clean Code, but the author is not addressing where this style actually provides benefits. When you're updating 23 if-statements because you had to add support for some new business workflow, you'll wish you had a conceptual entity that encapsulated the operations on the type of workflows so you just had to implement them in one place.
It's a problem chosen by the author of Clean Code. How is it a strawman? The author of the article is directly refuting the style of the problem/solution that the original author chose, and arguably demonstrated a better approach. That is not a strawman.
Indeed. The problem is trying to apply the principles of "Clean Code" or more generally of OOP everywhere. There are surely cases where having an interface as an abstraction and multiple implementations makes sense. There are case where it doesn't, and if you take as a dogma "everything shall be the implementation of an interface" you get more complex code and performance penalty for nothing.
There is nothing wrong with having procedural code with a switch case, as there is nothing wrong in having global variables, in having even goto, depends on how you use it.
> When you're updating 23 if-statements because you had to add support for some new business workflow, you'll wish you had a conceptual entity that encapsulated the operations on the type of workflows so you just had to implement them in one place.
Polymorphism won't get rid of the 23 if statements, it will just replace them with 23 method implementations. Then when you try to serialize that "conceptual entity" to a file or network socket you'll yearn for the if statements once more.
The main benefit of polymorphism is that it allows you to modify one part of a program without recompiling the other parts. In the absence of pre-compiled modules, polymorphism is isomorphic to branching/switch statements:
I stopped reading as soon as I saw the shape class. This example (along with the proverbial animal) has done a lot of harm to OOP and programming. You need base classes (which are not always the right answer, but when they are) to be based on the abstract concept you need to model not something real that is easy to understand when someone isn't an expert in your domain.
I'm not sure I follow the thrust of the article. The author starts off with talking about clean code, but then compares OO with procedural code. It's not the same thing, and of course we've always known that OO abstractions carry a performance penalty. Even the founders of OO (Alan Kay et al.) acknowledged the memory and compute impact, but thought it was a worthwhile tradeoff for clean abstractions in complex code-bases.
Back then computers were far less performant than they are today, so the first languages (e.g. SmallTalk) had to be compiled into a bytecode VM that ran on a Xerox PARC. Other efforts included hardcoding some of the constructs into the ISA.
How much of the performance differences come down to language or compiler choice in these examples?
Would I see the same kinds of performance gains or losses avoiding or using certain patterns in Go or Rust or Java? Are they the same examples as in C++?
What about dynamic languages like ruby or python or javascript?
Performance vs. Maintainability is the infinite debate, and it’s a mind numbing one because in the vast majority of professional roles you will have the opportunity to prefer neither.
I think performance generally trades along a different axis: open-world vs closed-world assumptions. There are many cases where closed-world assumptions may confer performance benefits, such as tree-shaking, whole program optimization, and using switch statements rather than a class hierarchy. Whereas designing for extensibility necessarily precludes some of those choices (though it doesn’t necessarily require OOP, for example registering a handler in a table). In other words, it’s easier to optimize a problem that is fixed and well-understood, versus one flexible and unknown. Take that ideas to the extreme and end up at ASIC bitcoin miners.
The only reason why your code is slow or bad - because you created it in such a way, not due Clean Code.
I cannot stop being surprised by how ridiculously short-sighted developers are - and how you continue to believe in golden hammers and silver bullets.
You want to build a car, so you take the “Clean Code” hammer and try to build one with it. Then you say, “Hmm, I built a car using the Clean Code hammer, but it cannot even reach 100 km/h. Therefore, Clean Code is bullshit.”
This is ridiculous.
The same applies to blind followers of Clean Code and SOLID who build systems without any high-level understanding of the system they are trying to create. The result is almost always an unreadable, unmaintainable pile of shit.
In fact, they are all in the same boat.
All of these principles are just that: principles. They are not specifications to be implemented. Moreover, they are LOW-LEVEL principles. So, they cannot be “bad,” “good,” “slow,” or “fast”. Your code is bad or slow - not the programming principles.
Until you understand what you are trying to build and how it should work, you cannot decide whether Clean Code, SOLID, GoF patterns, or any other principles are appropriate.
Once you have a solid architectural backbone that satisfies the required system characteristics, you can apply the principles that help you implement that design in the simplest and most effective way.
And each principle has its own trade-off with other principles!
--- too much DRY -> dead coupling (all these “cores” and “libraries” that team leads cobble together at night and proudly turning a distributed system into monolith)
--- too loose coupling -> excessive fragmentation -> low cohesion and broken incapsulation
--- excessive SRP -> low cohesion
and so on and so on.
So it is not Clean Code bad - you just not understand what Clean Code and other principles are.
It seems like the main takeaway is that many textbook OO paradigms aren't the most optimized representations of the code. In this case, the cost is dynamic dispatch and pointer-chasing. This is a function of the Shape abstraction, but not the abstraction itself.
But the argument is you're trading some of that performance optimization for maintainability. None of this is exactly news. And while I'm here ranting: I never understood why shapes are the canonical OOP example. Shapes are a closed set of types (yes I'm sure GPT-324 invented a new one) with an open set of operations. There's always going to be one more thing you need to do with those shapes, but you'll never be adding new shapes down the road unless you are still in Kindergarten. OOP is useful for the exact opposite case, where there is a relatively fixed set of operations and you routinely introduce a new subtype that needs to perform all or most of those operations.
I've noticed that most courses that introduce the concept of OOP do so in a way that (perhaps unintentionally) emphasizes the false notion that everything should have an 'x-is-a-y' taxonomy before actually asking the question if that is appropriate. Putting the Cart extends Vehicle before the Horse extends Animal.
If you care about performance, you don't use OOP, you don't use if/else, you don't use switch{case}, what you do is you write the hot parts in assembler.
If you aren't writing it in assembler, you're writing slow code.
But that code is still not optimised until you've implemented it in an ASIC.
Please note that this criticism is from 2023, but the “Clean Code” book has a second edition from 2025, extensively revised to account for the many misconceptions which new programmers might have gotten from the old edition, such as interpreting rules too strictly, etc.
I think the author comes from a very specific perspective; RAD tools, as I understand it, generally has only one, or a very few, software engineers per product. The way I would write code on a personal project is very different than the way I’d write code in an environment with changing team members, interns, guest commits, etc.
Also, In real-time simulations (ie games) often then way you write code can be the bottleneck. In web services the bottlenecks are more often network calls, database model, etc.
its a bigger problem of class based abstractions...cpu thinks in terms of arrays and lanes and indexes thats literally what a pointer is...when you try and abstract that away it in the wrong way that compiler cant understand, it creates overhead. but i think this is overall for all patterns in programming classrs are just so low level people take it for dogma and are appalled that something so standard is anti pattern
55 comments
[ 1.9 ms ] story [ 31.6 ms ] threadHN post for original article on 2023-02-28 (https://news.ycombinator.com/item?id=34966137), 739 points, 914 comments
Discussion between Casey (author of this article) and Uncle Bob (author of _Clean Code_, whose programming patterns Casey is critiquing), posted on HN on 2023-03-11 (https://news.ycombinator.com/item?id=35105528), 223 points, 213 comments
"Horrible Code, Clean Performance", a "homage" to Casey's original article, posted on HN on 2023-04-19 (https://news.ycombinator.com/item?id=35596069), 121 points, 114 comments
Is the most non-sensical thing I've seen. So of course the junior dev parade thinks it's the gospel.
Had a terrible manger who would swear by this book but couldn't code his way out of a paper bag.
On a long enough career path, eventually you will run into one Clean Code zealot who carries an air of superiority and nit picks every PR over things like a function having more than an arbitrary number of lines in it instead of reviewing the actual code. This is the point where most people come to hate Clean Code.
The bureaucrats who above all value process over outcome.
Functions always being under 10 lines just means you've got functions everywhere, which can be mentally exhausting to follow logic, if you aim for like 40 lines tops you can write better "stories" with your code that are easier to follow and more expressive.
Clean Code is unhelpful to beginners too, though: misuse of industry-standard terms, shunning of comments in favor of tiny functions with long names, shunning function arguments in favor of mutating state, polymorphism obsession, etc. So much of the concrete advice the book gives is just plain bad.
The reason people get more pissed off at Clean Code than they would at any other book that gives bad advice is the preachy and authoritative tone it uses. It frames people who don't do "Clean Code" as unprofessional and lazy, and this framing is very convincing to some people, as evidenced by some of the replies in this thread.
Well, isn't that the whole reason for the profession? If you want to be professional and hardworking, why are you instructing a computer to do your work for you? If you aren't being unprofessional and lazy, you're in the wrong business.
There is still good meat-on-the-bone in the book, and especially interesting if you’re working in a legacy system from that older time and place.
Those 2 years were to do the data migration and of course there were different priorities, if that would be priority we would do it in 1 month, but we liked to spread risk over time.
Reason was of course performance issues and not really needed joins, where normalization didn't bring any benefit.
Pragmatic approach: generally end up with an average of around 2.72 normalization across all tables.
Even Robert Martin, very often in his videos and blogs, espouses "engineering judgment" and is quite fine abandoning advice in his book when the situation calls for it.
If performance is important and clean code is impacting it, he won't object to your breaking the rules.
It's mostly with his TDD evangelism that he goes (a little) crazy.
Every time you over scope a function signature, because you want to handle that other case, you add mental tax to the next person. This accumulates, burns time, and now confuses agents, which is time and tokens ($).
No one wants to work with a dogmatic individual but I’d rather a nit picker than a human or agent slop machine.
I worked with a guy who would constantly drop niche jargon and quotes from famous engineers and then kind of smugly look at you. He could not write a function without proudly saying what principals it was following. He was a horrible programmer, ended up getting laid off.
Consider how the workload is now dominated by the core task of actually calculating the area, reducing the impact of struct usage.
Consider the diffs required to make this change.
It's not like Clean Code should be taken as gospel but this micro-benchmark is not a realistic example of what CC is trying to solve.
There is nothing wrong with having procedural code with a switch case, as there is nothing wrong in having global variables, in having even goto, depends on how you use it.
Polymorphism won't get rid of the 23 if statements, it will just replace them with 23 method implementations. Then when you try to serialize that "conceptual entity" to a file or network socket you'll yearn for the if statements once more.
The main benefit of polymorphism is that it allows you to modify one part of a program without recompiling the other parts. In the absence of pre-compiled modules, polymorphism is isomorphic to branching/switch statements:
https://en.wikipedia.org/wiki/Expression_problem
Why have you drawn the conclusion that the author is against this? A function with a switch-statement can do this.
those apps tend to be vastly more architecturally complex in almost every way compared to your average corporate or web app too.
Back then computers were far less performant than they are today, so the first languages (e.g. SmallTalk) had to be compiled into a bytecode VM that ran on a Xerox PARC. Other efforts included hardcoding some of the constructs into the ISA.
Would I see the same kinds of performance gains or losses avoiding or using certain patterns in Go or Rust or Java? Are they the same examples as in C++?
What about dynamic languages like ruby or python or javascript?
I cannot stop being surprised by how ridiculously short-sighted developers are - and how you continue to believe in golden hammers and silver bullets. You want to build a car, so you take the “Clean Code” hammer and try to build one with it. Then you say, “Hmm, I built a car using the Clean Code hammer, but it cannot even reach 100 km/h. Therefore, Clean Code is bullshit.”
This is ridiculous.
The same applies to blind followers of Clean Code and SOLID who build systems without any high-level understanding of the system they are trying to create. The result is almost always an unreadable, unmaintainable pile of shit. In fact, they are all in the same boat.
All of these principles are just that: principles. They are not specifications to be implemented. Moreover, they are LOW-LEVEL principles. So, they cannot be “bad,” “good,” “slow,” or “fast”. Your code is bad or slow - not the programming principles.
Until you understand what you are trying to build and how it should work, you cannot decide whether Clean Code, SOLID, GoF patterns, or any other principles are appropriate. Once you have a solid architectural backbone that satisfies the required system characteristics, you can apply the principles that help you implement that design in the simplest and most effective way.
And each principle has its own trade-off with other principles! --- too much DRY -> dead coupling (all these “cores” and “libraries” that team leads cobble together at night and proudly turning a distributed system into monolith) --- too loose coupling -> excessive fragmentation -> low cohesion and broken incapsulation --- excessive SRP -> low cohesion and so on and so on.
So it is not Clean Code bad - you just not understand what Clean Code and other principles are.
But the argument is you're trading some of that performance optimization for maintainability. None of this is exactly news. And while I'm here ranting: I never understood why shapes are the canonical OOP example. Shapes are a closed set of types (yes I'm sure GPT-324 invented a new one) with an open set of operations. There's always going to be one more thing you need to do with those shapes, but you'll never be adding new shapes down the road unless you are still in Kindergarten. OOP is useful for the exact opposite case, where there is a relatively fixed set of operations and you routinely introduce a new subtype that needs to perform all or most of those operations.
I've noticed that most courses that introduce the concept of OOP do so in a way that (perhaps unintentionally) emphasizes the false notion that everything should have an 'x-is-a-y' taxonomy before actually asking the question if that is appropriate. Putting the Cart extends Vehicle before the Horse extends Animal.
If you care about performance, you don't use OOP, you don't use if/else, you don't use switch{case}, what you do is you write the hot parts in assembler.
If you aren't writing it in assembler, you're writing slow code.
But that code is still not optimised until you've implemented it in an ASIC.
Also, In real-time simulations (ie games) often then way you write code can be the bottleneck. In web services the bottlenecks are more often network calls, database model, etc.
What did Bob ever ship that gives him any gravitas or authority in this area?