Hacker News new | past | comments | ask | show | jobs | submit login
“Clean Code, Horrible Performance” Discussion (github.com/unclebob)
223 points by rinesh on March 11, 2023 | hide | past | favorite | 211 comments



It is debatable if Clean Code actually improves the programmer efficiency and programs readability. I find people applying it religiously often create over-complex designs like FizzBuzz Enterprise.

Even Uncle Bob's examples are not the state of the art in readability: https://qntm.org/clean

The main problem seems to be that Clean Code is mostly a premature optimisation in code flexibility. It makes code more complex and objectively worse in hope it would be easier to extend later. Unfortunately we often dont know how the code will change, and in practice the code has to be significantly changed/rewritten anyway when a business requirement change appears.

IMHO optimizing for simplicity and readability has served me the best. Instead of avoiding the changes in code, it is better to write code so obvious that anyone can safely and easily change it when really needed.

And finally, performance of the program vs performance of the developer is a false dichotomy. So many times I've seen a more readable, simpler code turned out to be more efficient as well. You often can have both.


I used to like to rewrite code that I thought was "ugly" because it was not written in the modern way or it was not very generic or whatever. I thought that doing that was often pretty easy so I thought "why has no one done this already?"

Later I realized that the fact that it was easy to change was what made it good and the changes I wanted to make to it would probably just make it more complicated.

That's the danger; good code is easy to change so it will easily get rewritten until it's not easy to change anymore.


> Software has a Peter Principle. If a piece of code is comprehensible, someone will extend it, so they can apply it to their own problem. If it’s incomprehensible, they’ll write their own code instead. Code tends to be extended to its level of incomprehensibility.

https://nigeltao.github.io/blog/2021/json-with-commas-commen...


> (...) because it was not written in the modern way or it was not very generic or whatever.

Premature generalization is a well known problem and a never-ending source of complexity. I lost count of the times I had to push back on PRs of clueless developers who felt the need to generalize one-liners that pop up twice in the whole project, and for that their proposal was to add a strategy pattern, ultimate replacing two expressions with three classes.


>good code is easy to change so it will easily get rewritten until it's not easy to change anymore

Sounds like the 'bad currency drives good currency out of circulation' problem. Bad code drives good code out, because good code is easy to understand and change.


> Bad code drives good code out, because good code is easy to understand and change.

I call this the Peter Principle of Programming:

"Code increases in complication to the first level where it is too complicated to understand. It then hovers around this level of complexity as developers fear to touch it, pecking away here and there to add needed features."

http://h2.jaguarpaw.co.uk/posts/peter-principle/


Very cool point.

I will highlight though that I have worked with people that choose the path of least resistance regardless of complexity of the code that would need to be changed. It's as if they refuse to read code.


Without concerted effort, any easy-to-change code naturally will be replaced by hard-to-change code.


exactly


This is why I think the most powerful abstraction for framework with extension points is c++ template meta programming. Wait! Hear me out!

Template meta programming is an entirely separate, incredibly complex, programming language distinct from c++. The only people I have ever seen use it in production are exceptionally skilled programmers. Everyone else flees and cowers. If they try to change it it will not compile. So why is this good? Because it only allows the framework to be extended in the ways intended by the original designer. Anyone wanting to update this unholy beast will be a master programmer who’s investing significant effort. That implies the updates will be good.

Good code is easy to change. Great code is near impossible to change, but easy to extend.


What happens when you have new requirements that weren't envisioned by the original designers, who are no longer there? How do you unravel the design to the point where you can create a new one?


With great effort. That’s kind of my point though. It’s when you are changing the design from the originally understood “these are the ways to extend and add”, to a new paradigm, that you should be expending great effort.

In such a code base, it quickly becomes understood that if changes are required to “framework Doom” in order to achieve a new feature, the time budget will need to be large. It’s built in tech-debt prevention.


The ITK experience


you just sent shivers down my spine


Shivers of excitement surely


that last paragraph is an excellent description of the problem!


Strong disagree. This is something people say to sound clever. It’s not true at all. The real cause is basic entropy and complexity are always increasing. Rarely does a project manager request a feature be deleted. This is compounded by most companies having a few good programmers and mostly mediocre programmers, so the codebase will tend toward the mean over time.


Hm... Ad hominem, truism, truism, ad hominem. And not even disagreeing with the conclusion in the end...


I'd say false dilemma fallacy.


so ... sounds like you agree?


What is the alternative? Bad code?

It's exactly same as arguing you should not educate your workers. Cause they might leave. So you get only the least knowledgeable ones.


It's really not.

Software solves business problems. Code that meets its requirements needs to be left alone. Developers just changing code because they feel like it need some coaching. They're wasting everybody's time for reasons that amount to "I like it My Way".

So, with normally performing developers, the only code that changes is the code that needs to change. Good or bad only has an influence on the cost of a change.


That's not inconsistent with "good code will get rewritten until it's not good code." Good code that you need to change is code that can degrade as a result of that change.

Nothing to do with "just changing code because they feel like it".


The good code will be changed after it has ceased to be good code too. And bad code can become good code in the process of changing it.

There is nothing that makes good code more or less likely to change than bad code.


Robert C Martin's (who is not my uncle) Clean Code book/advice is what I would call junior programmer material. Its good to get someone started to think about better ways of writing software (albeit is hasn't aged very well).

I don't recommend it to juniors anymore because it hasn't aged well and is for my taste hyperbolic in its promises.

IMHO clean code also is more focused on code implementing "business logic" than "systems programming" for example (probably a bit tricky to actually define what the difference is between these too).


Just yesterday, when discussing our company's grades, someone joked that the difference between a junior and a senior developer is that a junior developer should learn to know when to use OOP/abstractions while a senior developer should learn to recognize when OOP/abstractions are to be avoided.


Used to be a similar joke in frontend a few years ago when css was at its complexity peak. Where the progression is junior: "I'll just use bootstrap", mid: "I'll write my own css custom tailored to the project", senior: "I'll just use bootstrap."

I also think some of this... attitude? learning? is what go is trying to activate/take advantage of. Everywhere I've worked has had some monstrously experienced senior dev who could easily crack out a solution to a problem of any difficulty but who refuses to use any abstraction more sophisticated than a for loop.

Go says you don't need anything more sophisticated than a for loop, in fact you can't have anything more sophisticated than a for loop. I guess I'm not there yet but it definitely seems suspicious that the people I know who are most into go are very early in their careers or deeply experienced.


I agree, but I would extend it a bit:

For a junior the answer is more "I'll just use bootstrap and customize as much as needed".

While for a senior it's more in line with "I'll just use bootstrap, but you bet I will be on the designer's ass if they ask to customize stuff where it's not necessary."

There is a reason the mid-level developer wanted to write custom CSS. And the reason is that they didn't have the political capital to make bootstrap alone work in a way that is good for the company.


Uncle Bob,the eternal noob of programming.


Which isn't a bad thing when you write books for noobs, writing resonates more with you when it comes from one of your own.


As an old-timer it is easy to forget that we all started as beginners and had to learn all the things which now are second nature to us. At some point the material which is valuable to a beginner seems trite, obvious and simplistic to a senior.


This begs the question: what do you recommend instead?


"Code Complete" by Steve McConnell and "A Philosopy of Software Design" by John Ousterhout.


I second this about Code Complete. (Also I think it's at its 2nd edition, maybe 3rd?)

It's more up to the point instead of going into dogmatic diatribe

It's more evidence based (giving examples, etc)

Go for this one and Bob's your uncle! (or maybe it stops being your uncle, I guess)


> "A Philosopy of Software Design"

What a great book. I second this and add my own "The Pragmatic Programmer: 20th anniversary edition"


"Structure and interpretation of computer programs", its not exactly in the same category but IMO is far better. At least it doesn't make you write tones of one line,single time used functions with 400 character length name.


Code complete, Steve McConnell


"The pragmatic Programmer" is one I would recommend.

That, and "Team Geek", in term of relationship with codes and teammates.


The pragmatic programmer is really keen on code generators. Which is the right thing to do. It's also very difficult to do in practice as the languages that are sufficiently impoverished to need external code generators are invariably bound to build tools that can't sanely deal with them.

edit: oh, and also it's hard to avoid people checking in and/or editing the generated code


Not the OP but HTDPv2 is a solid recommendation here


I don't understand why you would acronym your book recommendation. Anyone this would be a useful recommendation to would not know about it already, and hence not understand the acronym.

For those wondering, "How to Design Programs"


Yeah fair comment. I guess i’ve fallen into that familiarity trap.


I also enjoyed the Hitchhiker's Tips for Deeper Pockets (v2).


Programming practice


I am not fond of Clean Code, but I am fond of Clean Architecture. It's a much smaller set of ideas, for a start.


I gave up on Clean Architecture after about one chapter. There's a section where he's graphing the number of lines of code in a hypothetical company's codebase over time - it grows rapidly at first and then it levels off, and he points at this like it means anything, specifically like it's a bad thing and it means the software has become difficult to work in. Also this isn't a line chart, it's a bar chart, and the X axis isn't time, it's unlabeled - eventually, in the text, you find that there's one bar per major release of the software, if that tells you anything about how retro this conception of software development is. Another bar chart shows the number of developers growing rapidly, as if that means anything either... It was just baffling.


I personally find that those two videos are much better at conveying the foundational knowledge needed to invent and understand something like Clean Architecture than the book itself:

https://www.destroyallsoftware.com/screencasts/catalog/funct...

https://www.destroyallsoftware.com/talks/boundaries

It seems unrelated in the surface, but I feel like they explain the "why" of a software architecture in a much simpler way and without the prescriptive and hand-wavy "trust me" nature of most software architecture books.


Thanks, this comment accurately captures my discomfort when reading the parts where Uncle Bob was talking. He consistently portrays Clean Code as a programmer friendliness vs processor speed tradeoff, while my impression of Clean Code has never been that it’s slow and programmer friendly, but full of overengineered design patterns that are horrible to read.“Premature optimisation in code flexibility” is a really succinct way of putting it.


I personally find it full of rules that make sense in isolation and can make your code look pretty when you're a solo developer, but are actively making it more difficult to make the program understandable and flexible in the long way.


So many times I've seen a more readable, simpler code turned out to be more efficient as well.

In other words, if it's easy for the CPU to read, it's probably going to be easy for a human too. They both stumble a little on indirection and jumping around.

The other advantage of simple (macro-level, and not "one line functions" micro-level simplicity) code is that it also tends to have fewer bugs, and what bugs do appear, are also easier to find and fix.


I disagree. Many abstraction makes it easier to read for human. Example, the first abstraction is to use functions, if/else, and loops instead of plain goto everywhere like the CPU like.

Now abstraction also have a cost that needs to be kept under control. (Some have zero costs, some have compile-time costs, some have different level of run-time costs) And of course, not every abstraction is a good one: Sometimes it can even be counter productive to hide details if these details are simpler than the abstraction itself.

In summary, it's all about trade-offs and not over-doing it.


> Many abstraction makes it easier to read for human.

Many abstractions are also conventions, just look at the js/react world. There is redux stores, mobx stores, hooks, class-based components, signals. All different abstractions with various performance and readability concerns. Someone used to one of them would say the others are not readable or maintainable. So maybe we can choose the more performant abstractions and teach them, so it becomes institutional knowledge.


> Example, the first abstraction is to use functions, if/else, and loops instead of plain goto everywhere like the CPU like.

CPU likes if/else and loops just as much as it like its gotos. If/else and loops are just conditional jump (with a comparison done before the jump), and gotos are uncoditional jumps. Both are very much machine code constructs, that's why they're in C (which is basically a syntactic sugar over assembly).

Function calls are slightly more involved, but basically also very, very low level.


you don't write

   if (!condition) 
       goto else_;
   // some code
   goto endif;
   else_:
   // the else block
   endif:
   // More code

Same for loop. That's what we mean by "goto considered harmful".


A problem with that is that compiler technology hasn't really progressed to do big whole-program or data-layout optimizations from high level specs.

If we were closer to the model where a program would read its fixed inputs (for the run) and it would be specialized in place for the input using partial evaluation, with profile feedback for data accesses to help transforming the internal data representations to optimize storage requirement and cache access patterns, we would be much more motivated to specify many things abstractly enough to give machine representations of the code and data more freedoms.


The whole C64 scene are shaking their head in disbelief. The CPU couldn't care less about "easy readability" (whatever that even means) of your code. Ever heard of self-writing / self-modifying code? Probably an extreme example, and it can be used both ways - for performance reasons (unroll those loops!) or for abstraction (macros are your friend!).


One thing you have to remember about modern CPUs is that they are not fast 6502s.


So why don’t we write plain old assembly then, ad absurdum?

Here are my thoughts in a bit more detail from a previous thread: https://news.ycombinator.com/item?id=35061151


I think we would be far better off in analyzing codebases that are considered well done and then try to reverse engineer some good practices out of these. Uncle Bob et all are just riffing on tiny sample projects that they work on in between speaking engagements. I’d trust the opinions of these types of people a lot more if they actually had achieved anything other than writing books and talking.


Your words are harsh, but they are true. Our industry has been a train wreck of mediocre ideas capturing mindshare and spread with religious fervour. Money, damned filthy lucre, is of course the reason behind this.

I don’t blame the players, but I do wish there was some way to inoculate the herd. Perhaps we need a proper religious tradition. Robes would at least make it fun.


Snake-oil salesmen have always existed in one form or another.

OOP was pushed very strongly in the 90s, for example, and people promising to create massive increases in productivity by providing consulting services for "OOP-ifying" code probably made plenty of $$$ off that fad.


>> Our industry has been a train wreck of mediocre ideas capturing mindshare and spread with religious fervour

This is a great comment. Quotable even.


I've seen examples of FizzBuzz Enterprise in practice, eg a small algorithmic challenge that reads in some data and finds a value was split into several files, one class to read the data, one class to run the algorithm, one file to print out the results, etc etc. What was supposed to be one function with less than 50 lines turned into a multi-class multi-file monstrosity. The Java coder doing it couldn't imagine doing it any other way.


> a small algorithmic challenge

For me that depends on the context:

* A challenge for an interview? No wonder people try to insert structure as if it were a bigger project.

* A challenge for a contest? Probably not a good solultion for most point systems.

* A challenge for fun on hackerrank or LC? Whatever you feel comfortable with works.

For structuring a larger system, separating data ingestion, data output and algorithm to work on the data, seems like a fairly entry level separation.


> Clean Code is mostly a premature optimization in code flexibility > in practice the code has to be significantly changed/rewritten anyway when a business requirement change appears.

Tying these two together. Clean Code doesn't distinguish between framework code and business logic code. Or more you should write the latter like the former. The problem is writing framework code is hard to do well. It needs to be well thought out, bullet proof, and importantly well documented. That takes a lot of time and effort and running the code under a number of different use cases.

One is better off writing business logic as straight forward and goal directed as possible.


That's interesting, this is the first time I've heard this critique, and I totally agree.

The coding culture around Clean Code seems to put a lot of effort into "building frameworks on top of frameworks" without really admitting it, and the "framework-y bits" are intertwined with business logic, like exposed piping in a building. But business logic should look more like Golang instead.


> I find people applying it religiously often create over-complex designs like FizzBuzz Enterprise.

To be fair, this can be said about any technique. Lots of bad code results from people taking a reasonable principle and applying it religiously rather than judiciously.


On the other side of taking Muratori advice too far, is the story of Mel. Or to be more precise the guy that comes after Mel.

You have a ball of highly optimized mud, that quantum collapses if you glance at it too harshly, and we need it to be USB compatible. By Tuesday. Good luck!


Interesting that he now prefers Clojure, where rules like "function names should be verbs" take a 180 and become don'ts.


This is pretty much my soapbox about DRY. Code is like clay, once it dries it’s not malleable anymore. Components that copy/paste but in doing so are self-contained can be fearlessly changed.

And above a certain size dry works against you because when making changes to component A that requires a slight modification of the behavior or component Q deep in the call stack is so painful because the blast radius is huge. Dry codebases end up slowly accumulating more and more effectively read-only code that makes implementing non-trivial changes require increasing amounts of cleverness until development slows.

I think the more general principle is more to do with code directionality and framework vs library but the motivation to franken-framework your code always seems to be dry.


Having worked with very non dry code where each instance has slight variations but none of the commonalities are captured.. I have to say it can cause a lot of bugs and a lot of headache to maintain.


Not only is it debatable, you never see any type of evidence. And how would you measure it?


Clean code is defined as code that improves programmer efficiency and program readability, eschews premature optimisation by optimising for simplicity, makes code less complex and objectively better by optimising for readability, allowing virtually anyone to safely and easily change it when really needed.

So you're actually a clean code proponent.

As is usually the case with such debates, it is a debate of differing definitions masquerading as a debate of differing arguments.


> Clean code is defined as code that improves programmer efficiency and program readability, eschews premature optimisation by optimising for simplicity, makes code less complex and objectively better by optimising for readability, allowing virtually anyone to safely and easily change it when really needed.

I think plenty of people love clean code. I love programmer efficiency and readability. But there's clean code and "Clean Code". Some of the code examples from Robert C. Martin's Clean Code book are absolutely atrocious. I would be horrified to find anything so unnecessarily abstracted and overcomplicated in my codebase.

Its precisely because I'm all for readability and programmer efficiency that I find the recommendations in "Clean Code" so bizarre and abhorrent.


His examples use class state as faux-global state and dozens of small one-line methods that "do one thing", but don't really, since they operate on this object state. In the end, it makes it difficult to follow the logic flow, since you have to jump around from one small method to the other to understand what's happening. And clear understandable method naming is of limited help, so it turns into this ravioli code anti-pattern, clean on the surface, but semantically all tangled up.


Ok. Fair enough. I just find that what Uncle Bob calls clean code is often not clean code for me. E.g. preferring inheritance over switch is less readable. Flexibility/extensibility and readability are different things.


> E.g. preferring inheritance over switch is less readable.

Surely this depends on the context? If you have a many different classes it tend to lead to cleaner code if you encapsulate the class-specific logic with the class definition rather than intermingled in multiple giant switch statements. In particular you can add new classes without making the rest of the code more complex.

Of course there are cases where a switch is the right choice.


Switch statements don't have to be giant. Hint: you can still extract each branch to a separate function / module. But what is more readable about them is the control flow: the condition is explicit and all targets are easy to find. A codebase using switches/ifs and function calls can be easily navigated with ctrl-click in most IDEs. A codebase relying heavily on interfaces and inheritance cannot.


If you aren't going to use polymorphism to vary behavior, but depend on conditionals, what do you use classes for? Just for hierarchical data encapsulation?


Afaik, data-oriented approach doesn't use classes to encapsulate behavior.


Surprise: I don't use classes. Rust doesn't have them. :P


So, you’ve been arguing for using switch statements, instead of Clean Code’s suggestion of using object-oriented polymorphism, in a context where polymorphism doesn’t exist?


Not at all. Not having classes is not the same as not having polymorphism. Rust supports polymorphism just fine (and one could argue its support for polymorphism is actually much more advanced than that of Java's; but that's not enough reason to use it everywhere).


The greatest trick OOP ever pulled was convincing the world that polymorphism didn't exist outside OOP.


I use multiple languages and I always consider the context of programming advice. I recommend a broadened perspective.

In general I would rather maintain code by someone who have read Clean Code than by someone who considers it beneath them because they are too elite.


How are structs in Rust any different than classes?


Behavior and data is more separated

A struct only contains data, however you e.g. can define methods on it or implement traits for it No inheritance.


They don’t only contain data though, you can define methods as you said. Yes, there is no-inheritance, but they contain data, maintain state, and allow for defined methods. 90% of the time people are talking about classes, they are talking about that.


So you are saying context doesn't matter, switch is always better?


If we talk about readability - it is hard to think of a situation when inheritance would be better - it adds an indirection, it hides what's really going on, and at the end of the day the code is also longer and more complex.

However it has some other uses, e.g. when you really want to support adding new cases without modifying the code, e.g when doing a library, you need some kind of polymorphism.

So basically - it is not that abstractions/indirections are inherently bad, but they come at a cognitive cost. And it depends on the context if this cost is justifiable.


> Surely this depends on the context?

Yes, it depends on the context. In this case the context is the switch statement described in the https://www.computerenhance.com/p/clean-code-horrible-perfor.... Too bad TFA never mentions it explicitly (other than in the title and in the video). Recently it generated a heated discussion around here.


Agree. Inheritance is a code smell. And clean code does not smell :)


Polymorphism is useful when switch statements are repeated, as a way of eliminating that duplication, not when there’s a single well-encapsulated switch statement.


I think of this as a cross spectrum (terrible, good enough, well done, overdone)

I've seen less production code and more discussions on code that focusses on opposite ends.

terrible readability and maintainability with extreme performance.

v/s.

terrible performance with overdone "design" abstractions.

OTOH, I've seen lot of production code that has good enough design, with well done perf and vice verse.

Not much talk about that stuff.


Like "Reduce. Reuse. Recycle." The logic behind "Make It Work. Make It Right. Make It Fast." is very deliberate.


I want to add reusability and removability. If I for example write some code that connects to a SMTP server to send an e-mail, I probably will re-use it in other places, even in other programs, so the code can't depend on global functions. The code should be independent. Then it also becomes removable and replaceable.


Clean Code led to the development of the VIPER framework, which is a crime it must answer for.


True. VIPER was the craziest thing I had to deal with when I developed iOS.



I'm starting to hate programming discussions, after you've read a lot of them they're predictable, boring as hell and you can argue endlessly just because you value a little bit different things.

This discussions is yet another, nothing special way of saying: context matters.

You have a list of requirements of what you want to achieve, some of them do appear during development and you develop against this.

I have completely no idea why "software part of the internet" is losing their shit over those 2 guys arguing since it doesn't seem to be "deeper" than 2 random people arguing in some random comment chain.


>context matters

I've wrote about my observations with the same conclusion

https://trolololo.xyz/programming-discussions

>I've started thinking about it and realized that people arguing on all previously mentioned platforms tend to have various backgrounds

>Web developers, desktop developers, system programmers, cloud engineers, people working at startups, people working in corporations, beginners, experienced and known in the industry people, FP/OOP fans, self-taught, people after electrical engineering/computer science/mathematics, and a looot of more.

>So, what's the difference? I'd say context and the context is unfortunately lost in those discussions because all you see is just somebody's comment and that's it. Nickname very often doesn't tell you anything (except on forums after you spend some time there)

>And they all are right (or may if you want to argue :)), but very often that information about their respective domains is lost and the argument is around some "generic code base" or some "average project" which is different for everybody.


Tab vs spaces war has cooled off, so we have to start a new one.

A: If I do X, then this happens...

B: Yeah but I don't care about X.

A: You are not a professional if you don't care about what I care about.

Now that this debate has raised some dust, I guess Bob has already started writing the "Clean Performance" book in order to continue to milk the series and stay in the spotlight.


Sounds like a "no true Scotsman" argument


you've probably never worked in an average enterprise java shop.

The religious refactoring of any kind of software to its most generic and decoupled design, under the name of "cleanliness", is quite impressive. it's quite important that one of the most well known figure recognize that those designs have costs in terms of performance and are not suitable anywhere.


That hasn’t been generally true for more than a decade. Where they still do it, they just have a shitty architect, and can and do happen regardless of tech stack.


This. Thank you. Also the amount of claims and self confidence in this thread by people not even able to back their claims with examples is hilarious imo.


At some point in my programmer career I figured out that optimizing for human comprehension, a.k.a. "clean code", is a valid goal.

I watched Casey's video in full and agree with all the points he made. But as others pointed out, he focus on squeezing every bit of performance in the context of real-time video game logic, and this isn't representative of every programming problem.

As an addendum to Casey's video, another popular technique for squeezing more performance is to invert the normal array-of-structs to a struct of arrays, as it tremendously improves SIMD/vectorization. https://en.wikipedia.org/wiki/AoS_and_SoA

For a lot of things that I work on, simply having correct, complete, working code is most of the battle. Execution performance takes a backseat to development time, data acquisition time, human analysis of the problem space and generated output, etc. So by default, I follow Knuth's advice that premature optimization is the root of all evil. I write clean code but go into Casey mode when the numbers justify it.


> At some point in my programmer career I figured out that optimizing for human comprehension, a.k.a. "clean code", is a valid goal.

Nobody is arguing against comprehensible code. Caseys video is not about clean code in general, but "Clean Code" as presented e.g. in the book by Robert Martin, which contains a bunch of code I would not classify as "clean" by any metric.

> But as others pointed out, he focus on squeezing every bit of performance in the context of real-time video game logic

He doesn't, though. The "Clean Code, Horrible Performance" video is an excerpt from his course called "Performance-Aware Programming", where he explicitly says many times that the course isn't at all about "optimization", but merely being "performance aware". This isn't highlighted in the video though, so getting the full context is difficult.


I don't think Casey would object to "optimizing for human comprehension". If you are willing to give up an order of magnitude of performance, you can probably just do it without much care.

I agree that "optimizing for human comprehension" is a worthy goal, but it is very hard to actually know what is easiest for humans to understand. I don't think that guidelines like "clean code" actually are particularly effective at making understandable code. My personal guideline is to prefer code which is "simple" for both humans and computers.

> So by default, I follow Knuth's advice that premature optimization is the root of all evil. I write clean code but go into Casey mode when the numbers justify it.

I hold the controversial opinion that Knuth's advice is not relevant to most modern programmers. Most modern programmers have never done the "optimization" that Knuth was referring to in 1974. Optimizing only the hotspots of your program is very relevant technique, but if you write terribly performant code everywhere, there won't be significant hotspots. Everything will be lukewarm.


> Most modern programmers have never done the "optimization" that Knuth was referring to in 1974

I don't know the exact boundaries of what Knuth meant, but there definitely is a kind of "premature optimization" which has nothing to do with squeezing out the last nanoseconds at a low level. When writing code it often feels like a good idea to make it reasonably efficient or flexible from the start, for example by using more specialized datastructures or creating more abstractions which makes the code using them a bit less straightforward. In my experience this usually backfires and only means I spent more time writing code before discarding the first iteration.

At first glance this is a different kind of optimization but the outcome is the same and so I think Knuth's famous quote can still be applied. Other than that I agree, optimization at the degree e.g. Casey Muratori made in the video referenced in this post is only relevant in tiny hotspots if at all.


Knuth very much pushed for efficient code. One need only look at the code he writes, to see.

Which is a large part of what makes that attribution amusing.


Since this gets repeated so often, it is probably worth reproducing a little more of Knuth's quote.

"There is no doubt that the grail of efficiency leads to abuse. Programmers waste enormous amounts of time thinking about, or worrying about, the speed of non-critical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

"Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code, but only after that code has been identified. It is often a mistake to make a priori judgments about what parts of a program are really critical, since the universal experience of programmers who have been using measurement tools has been that their intuitive guesses fail."

The context for this statement, is of course his article [1] saying that goto statements should not be regarded as bad for religious reasons, but should be used appropriately. How common is the pragmatic approach to goto today, vs the "religious" response to goto?

So this is the context in which it's appropriate to quote Knuth on this: if you are thinking about efficiency all the time, making intuitive guesses about where your programs will be slow, using measurement tools after the fact, and in danger of spending your optimization efforts on 100% of the code instead of 3%, and if you have a rational rather than emotional response to seeing goto statements in your codebase, then Knuth's quote is for you. What he was really arguing for is a practical, rational approach rather than religious, emotional responses. Which was also Dijkstra's point about goto. The whole article is well worth a read if you haven't already seen it.

[1]: https://dl.acm.org/doi/pdf/10.1145/356635.356640


I'd go further and focus on "small efficiencies" part of that. The assumption used to be high that you would stay cognizant of large efficiencies.

In a very real sense, this is like a lot of the rhetoric around Big O analysis. From all that you ever hear online, you would think that Knuth's analysis was focused on only the Big O descriptions of algorithms. Reading the book, however, you find much more detailed takes of individual algorithms. Such that the aim was never to not be able to do the small, but realize that the comparison is dominated by the big.

Such is it with efficiency. The assumption was that you did not pick purposely inefficient methods, at large. And that you can focus on the very small level details where you get the best return on them.

Now, I think "clean code" is getting a bit thrown under here. Picking the specific examples that were easy to talk about in a pedagogical manner is not doing any favors to the general ideas from either side. And I consider myself mostly on the anti side of the "clean code" debate.


Much like Chuck Norris references in movies from 2004, I get the sense that this advice is a product of its time. One of those “it makes more sense if you were there” things. (It even starts off acknowledging that it’s an article about current trends!) Not to say it doesn’t generally still hold true, but this part stuck out to me:

> Programmers waste enormous amounts of time thinking about, or worrying about, the speed of non-critical parts of their programs

I don’t think this is widely true today (outside e.g. game development anyway). If it were, we’d probably have lots of fast software that’s very hard to read! But computers were a lot slower then, compilers weren’t as advanced, and it would make a lot of sense if performance was top of mind for most programmers at the time.


> I don’t think this is widely true today (outside e.g. game development anyway). If it were, we’d probably have lots of fast software that’s very hard to read!

I think the point was that programmers' intuition about performance is wrong. So we'd wind up with a lot of slow, buggy software — buggy because the code became hard to read when it was prematurely optimized.


That quote isn't saying not to optimize. It's saying not to optimize at the start.

When you initially write your code, you won't know where the bottlenecks are. So first choose and write a sensible implementation using appropriate algorithms and data structures to complete the task.

Then, when you have something working, measure its performance against real world data, not against synthetic benchmarks. You can use synthetic data that models the real world (e.g. a large number of user records in the system) to amplify the performance issues, but collecting the data from real world data will be better.

With that performance profile, you can see exactly where the performance issues are. Those will then allow you to write more complex, or harder to read code, that improves the performance of that part of the codebase. This will then allow you to write code that actually improves performance, and not things you think will improve performance, such as:

1. The optimized inverse square root function in Doom (https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overv...)

2. Improving the git performance of the sha1 algorithm. (There was a discussion about rewriting the old code a while ago in assembly in the mailing lists that I can't find due to Google not understanding my search queries. In those discussions, IIRC Linus ended up creating a C implementation that compilers were able to compile into an efficient assembly version.)


The quote specifically calls out "small efficiency gains" as a premature optimization. Specific example is a goto statement that is not as "clean" as many other control structures, but still makes a big impact on a particular implementation of a routine in question.

Implicit, I argue, is the idea that you were also not reaching to methods that were adding inefficiencies. That is, I think the argument is fine that you can and should try to write efficient code at a high level.


> At some point in my programmer career I figured out that optimizing for human comprehension, a.k.a. "clean code", is a valid goal.

I fully agree that human comprehension should be a prime objective for code. Alas, following the tenets of “Clean Code” produces anything but that.


I know that the original "MVC guy" and "Bob's nemesis" :^) have been looking into making human comprehension the main goal of a new programming language, "trygve", named after Trygve Reenskaug (MVC).

https://en.wikipedia.org/wiki/Data,_context_and_interaction


> invert the normal array-of-structs to a struct of arrays

We need our languages and compilers to give us the necessary tools to abstract away complexity without losing performance, which our current OOP languages don't really do.

Jai for example offers a special keyword, so the code is written one way (AOS) but the memory layout is converted into SOA.

https://pixeldroid.com/jailang/overview/Features/SOA/#/overv...


> Jai for example offers a special keyword, so the code is written one way (AOS) but the memory layout is converted into SOA.

I believe Zig has a stdlib function that allows you to do this, and I'm pretty sure there's also a Rust crate that provides a proc macro for it.


I think Clean Code is more about the ability to easily change the code as requirements change. Readability is one factor, but many of the rules really are only about ease of change. Open/closed and dependency inversion are prime examples of guidelines that are all about ease of change.


From what I understand the main benefit of struct-of-array is that it is more cache and alignment friendly.


I don't recommend to juniors any of the books of people like Uncle Bob, Fowler, etc...

Those are full of advices that seem reasonable but extremely generic and tend to be followed with religious fervour by people with limited experience resulting in an unreadable bug-ridden mess. When I think about those authors a single question comes to mind:

What have they ever built?

Reading code from popular opensource projects and evaluating the different approaches they took is way more useful than reading those books, full of regurgitated wisdom from people with minimal street creed. And yes, maybe it's time to stop calling him "uncle", it's not your uncle, he has very little to teach. Start building, stop reading.


> What have they ever built?

You have to be careful with that though. We've had a few people who'd "channel Torvalds", so to speak, by parroting his opinions with abrasive fervour. Any dissenters were treated as either thinking they knew better than him, or being ignorant of his work, or not having an appropriate appreciation for his work. And since Torvalds is very opinionated, so were they. It wasn’t exactly a fun work environment.

I’d also like to challenge the premise of the question. Being a maintainer for example is just as valid as being a “builder.” In fact, you’ll probably gleam more wisdom from being a maintainer than a builder since you are, by definition, trawling through other people’s code and maintaining it.

Look, it’s perfectly valid to consider someone’s body of work while considering their opinions. But dismissing them out of hand is wrong, in my opinion.


I agree with everything you said, especially with how important the role of the maintainer is to get something that keeps working over time (both are builders). And yes, holding someone else opinions strongly doesn't make you instantly a clone of Torvalds, thing that in workplaces with n>1 employees might not be desirable.


> What have they ever built?

While I agree that reading code and learning is essential, I don’t think this line of criticism is fair.

I mean like most programmers I’d imagine their years and years of industry work isn’t public. I mean what have I ever built?

Fowler was CTO of ThoughWorks for years and afaik they do really good and deep work, their content is quality. Bob Martin had a similar track record with 8th light, and as an organization is interesting.

That said, I think lumping them together is misguided. I’ve read a lot of both. To me unky bob is can be a divisive gatekeeper who wants to be right, while mFowler is boon to the industry, literally writing the book on refactoring. He has well reasoned and measured arguments.

I mean I think while formulating your own voice is important and essential, it’s also essential to learn from others and pass down knowledge. For some reason, I think our industry sees it as a threat.

Unky bob is a divisive gatekeeper who I suppose did years of industry work? I guess?

To lump Fowler in with unky bob, I just don’t see it.

I mean Fowler I think t started thoughtworks.


> I mean what have I ever built?

Most of my work is not public too, it doesn't have to be.

> I mean Fowler I think t started thoughtworks.

Yeah, I've been too harsh comparing the two, but as someone that have to fight daily with the consequences of the popularization/normalization/blind adoption of the microservices (his contribution was huge in this field) architecture I'm not sure anymore if his contribution is a net positive. The book on refactoring was great though, product of a different era.


I think the difference in the material of both is quite stark too.

Fowler's books are very descriptive, Refactoring is clearly a list of strategies. And there's even conflicting advice, since it's meant to be a catalog rather than a rule book.

Clean Code on the other hand is very prescriptive.

Not that it matters, the people that wrote the GoF Patterns book have been saying for years that their book is descriptive but very few people hear it.


Just read thoughtfully and treat the ideas as tools rather than laws. Reading code is good too but one doesn't have to jump straight to first principles.


I read through a decent chunk of Patterns of Enterprise Application Architecture for class in college in ~2006 and while I was indeed young and impressionable, it left a very bad taste in my mouth for heavily OO’d systems to the extent that it had the opposite effect - I largely avoided them likely longer than I should have.


All the concentration on "developer productivity" has lead to a massive loss of user productivity and wasted energy. Ironically, a lot of developers are also users, so they suffer just the same.

The worst part is, AFAIK it's not even clear that the recommendations in Clean Code are better for developer productivity, insofar as they create excessive complexity.

Another thing to note is that I've observed a significant fraction of users (and developers!) seem to have a very poor perception of time. You can replace an app they were using with one that is otherwise exactly the same but introduces a whole second of delay when interacting with the UI, and they wouldn't notice unless you gave them the two versions to compare side-by-side. Of course another significant fraction does notice, which is why you often see both "this new version is horribly slow" and "works fine for me, it doesn't feel any slower" opinions.


The “not all parts have to be performant, so you can make those parts clean” objection never sits well with me. And it’s not just the fact that clean code is presented as universal and this claim is only introduced after you complain that clean code has made your code less performant (Casey was remarkably polite in pointing this out and Bob was very gracious in concurring, both of which were a delight to read).

The reason it doesn’t sit well is that, in practice, it seems like most products don’t know what needs to be fast and so developers end up making almost everything slow. Casey gives many examples but the one that sticks out most in my mind is the Visual Studio watch window (about 25 minutes into https://m.youtube.com/watch?v=GC-0tCy4P1U). The watch window lets you pin variables you’re interested in and it will show you their current values as you step through the program’s execution. Invaluable for debugging. Visual Studio’s watch window is incredibly slow, to the point that it actually has a debounce so it will stop updating when you’re stepping quickly and only update once you’ve stopped quick-stepping. This seriously impacts its usefulness! To me, the watch window seems like it’s obviously one of those modules that needs to be running in milliseconds or less - but the company and/or the developers don’t see that, and shipped it slow (and presumably clean) instead. So in my mind, “write clean except where performance matters” comes with an almost fatal caveat, something like “you won’t know where performance matters”.


> the company and/or the developers don’t see that, and shipped it slow (and presumably clean) instead

That is a giant presumption.


It is slow, and I presume that’s because competent developers wrote it clean. It’s quite possible that it’s not clean either and was just written by developers incapable of performance or cleanliness. That possibility doesn’t detract from my argument - there’s no point in discussing performance or clean code with them if they’re incapable of either.


There is a lot of inherent complexity there though. Inherently VS needs to support multiple debugging engines and multiple transports to communicate with the debuggee as well (remote vs local debugging). That's OK, two layers of dynamic dispatch is still fast in this context.

Do we update call stack and locals window at the same time, or do we update each one as early as possible but show inconsistent data?

Do we fetch call stack for all stopped threads, or do we wait until the user chooses to focus on a different thread/display all threads using Parallel Stacks windows?

If Watch result is a collection, do we load its members or wait for the user to expand the tree?

If call frame has changed, the strings in Watch need to be re parsed to refer to new variables. Do we do this work every time, or do we optimise for stepping in a single function? Is it worth it if only a few of the debugging engines can support that optimisation?

Each Watch needs to be interpreted in a way that catches all exceptions and creates an error message instead of crashing the debuggee.

If the user puts a breakpoint at the end of a loop and holds down F5, watching the Locals to decide when to switch to stepping, is that a supported use case?

These are the product decisions that lead to the performance, not micro details of how the code was laid out to achieve the goals. I mean, I don't have access to their source code, so I could still be proven wrong. What's an example of a fast debugger?


>What’s an example of a fast debugger?

My apologies in advance if this feels like a gotcha, but… Casey ended up swapping to RemedyBG for debugging. He made a video about why. From about 0:50 onwards he talks about the speed and feature set of the watch window https://m.youtube.com/watch?v=r9eQth4Q5jg

Also, I don’t mean to be rude by ignoring all the questions you posed; they’re good questions, I just don’t have answers for them (I would hope the developers whose full-time job it is to build debuggers would, though).


> Casey ended up swapping to RemedyBG for debugging.

Thank you! I had no idea it existed.

The watch window in VS is slower on my 2GHZ laptop than the one in Turbo Debugger was on a 10MHz 8086. That simply makes no sense. I know that the compilers do more optimization and that the debugging info therefore has to be more complex today. It still makes no sense!


Don’t get me started. I know I’m sounding like an incurable fanboy for Casey because I mention him in every comment, but it’s only because I am an incurable fanboy for him, his polemics about software being thousands of times slower than it should be these days strike a deep chord with me.

In one of his rants he goes to the effort of booting up a version of Visual Studio from 20 years ago, on a 20 year old machine, to demonstrate that it really truly was way faster back then than it is now: https://youtu.be/GC-0tCy4P1U at 36 minutes in.


> https://youtu.be/GC-0tCy4P1U

That was a beautiful rant :)


Of course it detracts from your argument, because your argument is using it as evidence that clean code is not good for performance.


No, Casey’s argument is that clean code is not good for performance. The counter-argument to Casey’s argument is that when performance matters, don’t write clean. My counter-counter-argument to the counter-argument to Casey’s argument is that performance is clearly missing even when it matters, so clearly developers aren’t able to figure out when performance matters, so the counter-argument won’t work.

Bob is saying:

  doCleanCode()
  // doUglyFastCode()
Casey wants to swap that around because it’s bad for performance:

  // doCleanCode()
  doUglyFastCode()
Responses say “fine, don’t do clean code when performance matters”:

  if (performanceMatters()) { 
    // doCleanCode()
    doUglyFastCode()
  }
  else {
    doCleanCode()
    // doUglyFastCode()
  }
I’m saying “most developers implementation of `performanceMatters` is bugged, it always returns false”. My evidence is that “here, look at all these cases where `performanceMatters` should return true and yet we’re obviously getting the results of the else clause”.

You’re objecting that I don’t know the bad performance in a given case is because of a `doCleanCode` call, I haven’t looked at the source, it could very well be for other reasons:

  if(performanceMatters()) {
    doDirectDrawToScreen()
  }
  else {
    doDispatchDebouncedStateChangeToUserInterfaceFramework()
  }

Can you see how that doesn’t detract from my argument? We’re obviously never getting to the `performanceMatters() == true` branch of the conditional, so putting Casey’s suggestion in that branch of the conditional means we never do it. It does not matter if my evidence for “we never get to the `performanceMatters` branch” comes from statements that include `doCleanCode` or not.


So you are saying not that “here they chose Clean Code” but “here they chose not to make good performance”. You’re arguing that the only reason for poor performance is that the developers didn’t think it mattered?


Sort of? I completely agree with Casey’s argument that Clean Code makes for poor performance, but I’m not making that argument here, he’s made it for me. I am arguing that many developers are really bad at knowing when performance matters, so if they implement the advice of “write clean code, except when performance matters” they are never going to think performance matters and always write clean code. Does that help?


I really do not understand why this is a discussion, why a video had to be made about it and why we now need an interview about this.

Clean code / readable code / whatever you want to call it is often at odds with performance. This has been a known fact for decades. Everybody is aware of this. And for most enterprise projects it just doesn't matter. The performance analysis discovered nothing new and added nothing of value


> Clean code / readable code / whatever you want to call it is often at odds with performance.

I disagree; or rather, I'd put it the other way around by saying that often you can get both clean code (by some metric) and reasonable performance. The patterns in e.g. the book by Robert Martin doesn't give you either, though.

> And for most enterprise projects it just doesn't matter.

It matters for the users. I use software that is slow for no good reason, and I'd like to live in a world where this is not the case.


Also, I feel, it's a whole lot easier to refactor "clean" or "readable" code for performance than the other way around.

Make it run.

Make it clean. (and if need be,)

Make it fast.


As a dark matter developer, I learnt it as:

Make it work

Make it right

Make it fast

My interpretation of this, so far, "make it right", is to make the code and design cleaner and refactor.

Then "make it fast" came into the play, iff, there was enough push.


it did add something of value.

uncle bob realized his "clean code" may have done a disservice with regards to performance. but i am not holding my breath on seeing a change come about soon.

it is possible to optimize for both performance and developer productivity. but everybody is leaving that out in the discussion.


Or you know. Have a toolbelt. You keep tools on that belt and you use them as necessary.

Making a simple service? It’s in the descriptor. Just keep it simple.

Making something large that has a bunch of people working on it? Time to pull out the toolbelt.

Making something super mission critical? Use the right tools.

No approach is perfect. That’s why you use what makes most sense, mixing and matching, to put together what works best for the task at hand.

Anything else is noise and just ignore.


Recent and related:

“Clean” code, horrible performance - https://news.ycombinator.com/item?id=34966137 - Feb 2023 (906 comments)


this was a very amicable and fruitful discussion

it's been chaffing me a lot last couple of years that it is so hard to learn about making performant code. It was nice of Julia Evans to write that very approachable strace zine [1]. I wished there was more of that kind of stuff. So I am happy Casey is doing a whole course on this stuff [2].

[1] https://wizardzines.com/zines/strace/

[2] https://www.computerenhance.com/p/performance-aware-programm...


Check out Mike Acton's work also.


Long time ago advices like Uncle Bob's seemed good to me.

Now I try to use OOP the least I can, I try to use the less abstractions I can and I try to make the CPU use the least number of instructions.

I am using a part procedural and part functional approach, keep data separate from functions that process the data, try to use immutable data where possible and minimize state changes.

I am trying to use a data oriented approach and I am more happier and productive than if I hade to apply clean code principles and software patterns on top of software patterns.


Yeah, not a fan of atomising code into so many classes as Clean Code recommends, the mental load is higher and its a lot harder to see what's happening.

And thata before considering the great analysis of the examples in this book from a few years ago, that showed that they don't conform to the clean code philosophy at all.

Lucky for me, I bought this and procrastinated so long about reading it, I've since found out it's not good, so I saved some time.


For me it is not concept to oppose. You can write optimized code but still name your variables "first_item" instead of u_fp_64_ptr like I often see in "optimized code"


When code needs to be heavily optimized, the fact that some variable is a pointer to a 64bit unsigned floating point number may well be more relevant for understanding the code than the fact that it points to the first item in some list.


But that is explicitly known by the type, we are not writing 90s era microsoft office (hungarian notation really should not be used).


The name of a variable is supposed to contain the most relevant information someone needs when looking at an expression. If the most relevant information is the type, then that should be name. The fact that the compiler knows the type doesn't help me understand the code if I have to scroll two screens up to see what it was.

Imagine you see some code masking the first 40 bits from the location pointed to by first_item. Does the name "first_item" help you understand why they are doing that, or would it be more useful to know that it is the first forty bits of an u_fp_64, so it's masking the mantissa and just keeping the exponent?


> The fact that the compiler knows the type doesn't help me understand the code if I have to scroll two screens up to see what it was.

Doesn't your IDE allow you to effortlessly see the type of a variable, either through inline hints, some sort of keyboard shortcut or at the very least, by hovering your mouse over it?

Admittedly I haven't professionally worked in C/C++ since university, but my understanding is that small functions can be (are?) inlined, removing the function call overhead. If that's the case, couldn't you write a 1-line function like "maskMantissa", which would clearly communicate what the code is doing without overhead?


Simple > Complex.

Every extra year I spend in engineering, it's becoming clear that this is actually one of the only few things that mattered in the long run. I would say this is one of the best if not the best advice in programming and even in life.

If clean code doesn't follow that, ditch.


What you say makes a lot of sense.

Now, to give a concrete example. There was a C++ PR introducing an interface taking an argument of type int representing a duration. I suggested using std::chrono::duration (https://en.cppreference.com/w/cpp/chrono/duration), and I was overruled on the basis that "an int is simpler". To have context if you are not too familiar with C++:

- std::chrono::duration is part of the C++ standard library

- It's a wrapper on top of an arithmetic type just giving it "duration" meaning. It's of the same size as the wrapped arithmetic type.

- Yes, you can argue it's more complex since it adds a bunch of templated code on top of a language built-in type.

I'm going to guess that's not what you had in mind with your "Simple > Complex" advise. If so, that's what happens with any advice, even with simple one liners: somebody will take it to justify some crazy decision.


Simple vs Complex should always be evaluated holistically.

Each file could be simple but if they are in 5 level of inheritance, then it's not simple as a whole. Same with 10 different way to do the same thing or 3 different class that can be used to the same effect, it's not.

Your case is clearly adding complexity as the new class have no reason of existence, as it's just rebuilding part of the standard library which your compiler would likely have included anyways. The suggestion is clearly a bad one but our industry is filled with people who are either incapable of adequately applying "Simple > Complex" or don't believe in it. That's why enteprise FizzBuzz exist.


I found it interesting how concrete Casey gets, and how he quickly identified the cause of the UI bug (+ a mitigation). Meanwhile, it looks like Bob was speculating in thin air and throwing out random terms (O(n^2)) trying to fit in.


But Bob was right that the problem was algorithmic. Checking whether a string ends with a prefix of an emoji abbreviation is a task that could easily be done in a microsecond. It’s apparently taking 100 milliseconds, which is 100,000 times slower. Even factoring in the overhead of JavaScript, you could easily add a 10x or 100x “clean code” penalty and still be nowhere near the level where it’s a performance problem – if the algorithm is correct.

By “algorithm” I’m referring to more than just asymptotic complexity. Even so, as for “O(n^2)”… well, I didn’t analyze the code myself, but judging by Casey’s analysis (or even just by the symptoms), it seems quite likely that the time taken to input a character is at least O(n) in the number of characters entered so far (if not worse). That makes the total operation of entering n characters take O(n^2) time. Bob does seem to be referring to the total being O(n^2) rather than each character being O(n^2), since the reallocation strategy he mentioned as an example would similarly take O(n) per character. In that context, O(n^2) is less of a guess, more of a reasonable assumption given the observed performance characteristics. And it’s a reasonable aspect to point a finger at, because lowering the asymptotic complexity would be an essential part of fixing the performance problem. Not sufficient by itself, but pretty much necessary.


Reading this conversation is like watching Casey skillfully and lovingly jailbreak Uncle Bob's GPT personality prompt, yielding a new and exciting performance focused alter ego which I admiringly dub "SPEEDY BOB".


The performance degradations is a clear harm and pervasive.

The dev productivity improvements are questionable.

So let's go do some harm!

(and that example of slow typing in a "big" paragraph is a big red cherry on top of a brown pile!)


It's just an anecdote but I think the emoji picker problem they explored is really what it is about. You can debate all day about whether Bob's clean code principles are valuable but the reality is that no pattern from any of his teaching will ever make a text editor get slow after just 300 typed characters in a single line.

I bet I'm Casey's life it happens often that he has to dismantle clean architecture in an application that is already quite fast just to squeeze out some extra performance. But that's not in the same arena of these every day performance annoyances.

It's some sort of variant on Amdahl's law. You could have a million lines of fairly performant code, and then in 3 lines someone fucks up and introduces an accidentally quadratic function into an emoji picker and your whole application will feel slow.

That's also the take away conclusion from this discussion. After listening to 8 hours of uncle Bob going on about clean code, he should pause and tell you to check over your codes performance when you're done. Since the clean code made you so productive, and your code so readable, it should be an easy thing to quickly check your performance.


Last time I checked programming had something to do with computer science. You could say its applied computer science. So I ask myself: how come that this discipline, already 50+ years old, has almost no consensus of how its output aka written code should be structured? Why are there no established standards or rules? Not a rethorical question, happy to hear your thoughts.


This confusion is easily resolved - just remove "science"


Indeed. What we do is more akin to computer voodoo.

Now let me wave a dead chicken over those server logs...


I shudder to even think of the cumulative ecological cost of Bob's line of thinking, here. Imagine if Henry Ford did this... we'd all be getting 0.3mpg, but hey... productivity!


Imagine software that was delivered 6 months earlier but is 2x slower than it could be. This lead to productivity increase of it's users of 2x during that 6 months. At the expense of some extra electricity use worth $100, they made $10M worth of real world productivity.

My point is... the thing that is much worse than software that is unnecessary slow, is the software that was not yet written at all.

Now ... I think some of the Clean Code ideas are meh. But their performance is not the only or even one of most important aspects of it.


The software is delivered 6 months earlier, and it's 2x slower than it needs to be. Then it continues to get slower, because the company making this code has a culture that actively disdains making software quick (and in any case, the programmers working there don't know how.) 5 years down the line, the software is 2000x slower than it needs to be, and millions of users are having a minute or more of their day wasted, every day, waiting for things to load and icons to move that should be happening in milliseconds. Additionally, the quality and velocity of their work is far lower because using slow interfaces feels like wading through mud, leading to errors and frustration. The total human cost over the next 20 years is on the order of tens to hundreds of thousands of quality-adjusted person-years. Now, you might say that the right move is to make the code run well once it becomes a problem- but empirically, I don't see this happening!


> preferring inheritance hierarchies to if/switch statements,

And then he goes on touting readability right after that. Bold.


The big tip-off that all is not as it seems is that he calls himself "Uncle Bob" Martin.

Functions should do only one thing. Sure, but what does that actually /mean/? If a function calls two other functions, then surely, by definition, it's doing two things? So how much a function is doing is a question of how far you stand back when looking at it.

Also, if you follow a rule of functions having only 2-4 lines, then you're going to have a lot of functions, and tracing through code paths is going to be like peeling back layers of an onion. So that advice is just wrong.

It's not even clear that long functions are a problem. Back in the early 80's my A level computing science teacher, who had worked in industry, said that there was no real evidence to suggest that long functions are less readable.

There was a joke going around some time ago about an interviewee who was asked how big a function would be. He said "I like to be able to keep it within my head." When asked to elaborate, he said "I put my head against the screen. If the function is longer than that, then it's too long." Although facetious, I think that's actually a good idea. A function should be at most a screenful, so you can see it complete on the screen.

Recently, I wanted to customise my own gopher client. I first messed around with one written in Go, but it was too complicated to adapt for what I wanted. I switched to a C alternative, which was still a bit too complicated. I decided that I'd basically re-write the whole thing in C++, using whatever bits of functionality from the C part that I thought useful.

If there is a magic formula to writing good code, then I'd say that the less code you have the better, and try to keep code reasonably decoupled. The problem with writing applications is that there's a tendency to be promiscuous in how you use objects. So, in essence, every part of the program relies on every other part of the program. There's no separability of design. It is better to take a "library" approach to things, where each "module" doesn't know how it is going to be used. You then have co-ordinating functions which stitch this functionality together. The code you end up with should be much easier to adapt.

It's also useful not to be overambitious with your project. Someone once said that the genius of Ritchie and Thompson was being able to obtain 90% of the functionality using 10% of the code. If you think parsimoniously in that way, they'll be a lot less code to wade through when you want to modify things.


> The big tip-off that all is not as it seems is that he calls himself "Uncle Bob" Martin.

Totally agree. I believe "Uncle" is a pretty ingenious piece of branding meant to paint him as an implicitly trustworthy and wise figure that I should feel endeared to. He's just a guy who has built a career out of offering his opinions on how others should do a job he's never done. Notice that his About page [0] doesn't mention a single piece of software that he's actually built.

He reminds me of a company I used to work for. It was a healthcare architecture firm that got sued so many times for their fuckups that they pivoted to being a "thought leader" in their industry. Instead of continuing to build hospitals, they focused on consulting and publishing articles with their innovative [1] ideas about how hospitals should be designed. A classic case of "those who can't do, teach".

[0] http://cleancoder.com/files/about.md

[1] I shit you not, one of their ideas was a "hover gurney" that was basically a giant quadcopter with a bed on it. It was supposed to be easier to move. Blasting germs all over the hallway with hurricane-force winds was apparently not an issue anyone thought worthy of consideration.


I love this video because it's a long-missing counter to Clean Code. In the software world, often someone makes a bold claim (like "clojure considered harmful") and someone rebuts it ("a response to 'clojure considered harmful'") and someone rebuts the rebuttal ("a response to 'a response to «clojure considered harmful»'"). Sure, we've had people criticize Clean Code before, but never a proper critique.


I’m surprised that such a thoughtful and civil conversation came out of this after the precipitating screed against Clean Code. Kudos to both sides for engaging constructively!


Some gems from the discussion…

> Long ago he wrote a book entitled The Design of Everyday Things. It's well worth the read. Within those pages he stated the following rule of thumb: “If you think something is clever and sophisticated beware -- it is probably self-indulgence.”

> You also asked me "why...". To the extent that I have not answered that above, I'll simply turn the question around and point out that it is probably for the same reason that your video was solely focussed on the amplification of performance to the strident denigration of every other concern. To a performance hammer, everything looks like a nail. ;-)

Overall a very amicable and interesting read.

It’s so easy to preach high level architecture without specifics, but also so very easy to cherry-pick a specific example where generic advice doesn’t apply.

I think it’s interesting that there is an almost fundamental disconnect between “easy to understand” and “fast and efficient” …and I’m absolutely 100% with Uncle Bob that bounded contexts for complex code is the solution.

This is the approach rust takes with unsafe code and it has proved to be an extremely effective principle.


> there is an almost fundamental disconnect between “easy to understand” and “fast and efficient”

In my experience, poor performance is often because the code is needlessly complex and does unnecessary work. In that case, there's no trade-off. You can both improve performance and readability by simplifying.

There may be trade-offs required to optimize for the absolute maximum performance, but you can get most of the way there without sacrificing anything. The vast majority of programs are nowhere near the pareto frontier between performance and readability.


Clean code makes it easier to find high level optimisations which can improve the order of performance, not just shave off a few percent.


From: https://www.computerenhance.com/p/clean-code-horrible-perfor...

> The speed differences range from 20-25x — and of course, none of the AVX-optimized code uses anything remotely like “clean” code principles.

If clean code limits your default performance to 20 times worse, those high level optimizations might not even be worth it.


The point is, without a reasonably clean starting point, you'll never get to apply those 20-25x optimizations in code bases beyond a certain size, because they've become a wasteland of mediocre micro-optimizations before you even get started.

So the sensible thing to do is to identify a component of manageable size, fence it off with suitable abstraction boundaries, and then apply optimizations WITHIN the confines of that component only.


If your performance-critical code is 25x slower than it could be, while using the same algorithm, it’s not “clean”, it’s just bad.


It's true. It's easier to make mathematical connections with algebra, combinatorics, etc. when you're reading short formulas and not being not knee-deep in hand-written SSE asm.


> It is economically better for most organizations to conserve programmer cycles than computer cycles

This is only true if "organizations" here means those which write the program. If you include those who actually use the program, I don't see how this sentence could be proved.


This is too polite... They're trying very hard not to offend each other. Don't base your opinion on the topic from this conversation, I think it's better to hear what each of them have to say "behind their backs".


I have a problem with software engineering use of the qualifiers “clean”, “simple”, “easy”, “decoupled”, “elegant”, among others that only appeal to aesthetic sense but are not quantified at any moment.


This exchange was more enjoyable than I anticipated it to be.

> I created this with vi and used 1,$s/ /:/g

> Because, I really am an old C hacker at heart.

%s/ /:/g would have been shorter.


Hardware may perform more operations per second, but inefficient software remains inefficient, and it's still wasting roughly the same amount of energy as it did on slower hardware, and waste only accumulates, skewing the performance of the rest of the system. That inefficiency gets multiplied by millions or billions of units worldwide that run this software.


>What’s more, processors are so cheap and available that it is a trivial matter to add more of them to a system.

I don't know what Uncle Bob uses but the motherboard in my PC has exactly one CPU socket. And at $600 I wouldn't say it's cheap.

I'm not sure how can I add another CPU to my laptop and my mobile phone.

Also not all software is multithreaded.


To give him the benefit of the doubt, maybe he meant a distributed system? Much easier to add CPUs then.


Function calls are expensive, more news at 11


i find it baffling that uncle bob thinks it's okay for dialog boxes to be sluggish. and that we can throw CPUs and GPUs at most performance problems. because it's "cheap".

that way of thinking is part of the problem.


What Casey doesn't get is that for the vast, vast majority of code out there, reliability, maintainability, scalability (to hundreds of servers, or hundreds of software engineers working on the code base), testability, and observability trump raw performance.

CPU and RAM are cheap. By engineering your software to scale performance as close to linearly as possible with the addition of more CPU and RAM, you've changed the problem from one that requires the best engineers to solve into one that can be solved literally by throwing more money at it. At the largest software deployment scales this is totally doable, and it makes the most sense for the business.


You've now pushed the problem to the infra team, thanks.

What happens when the spot instance that pops up is a much older generation, and your app suddenly runs far slower due to reduced memory bandwidth, CPU clock speed, etc.?


This is the second critical thread about Uncle Bob in the last month or so. (Maybe there have been more.)

Why digging at ancient books? Is there something going on here?


Enterprise Code should be concerned with getting the best "order" of performance (i.e. O(n) over O(n^2)), but beyond that design for brevity and clarity.

Architecture wise, the Clean Architecture is a good starting point, I would enforce one or two rules - maybe domain seperation and business logic/driver seperation, but not go more zealous then that.


"constrained problem within a resource constrained environment" is a good phrasing


"Clean Code for me the testable code."


Rule 1: if it works, refactor it


The only clean code is the one that does nothing.

Otherwise you can only struggle to add as little dirt as possible.


Here's another controversial opinion:

It's the genius programmers who write the shittiest code. In my experience clean code tends to be a waste of time for geniuses because shitty code isn't really a problem for smarter people.

The further away you are from genius the greater the tendency for you to write cleaner code because you need it in order to deal with the complexity.

What's common among HN readers is that they think they're smart. So you may be reading this and thinking "Wait a minute, this isn't true! I'm smart and I like clean code!". Well, I hate to break it to you. The truth hurts because most likely one of those two attributes probably doesn't actually describe you.

Also as a side mention, I'm a clean code Nazi. My code is really clean.


This was funny. But I've seen too much clean code written by people smarter than me to agree.

I actually think how "clean" your code is depends on lots of factors. Eg.

(a) Do you care if your coworkers find it easy to modify your code?

(b) Do you feel a sense of ownership over the code you're touching?

(c) Does your organization reward delivery speed without any checks for code quality? (eg. no culture of code review)

(d) Is the code a proof-of-concept that needs validation from users before further investment?


You're not dealing with the geniuses. You're likely just dealing with people smarter than you. I can assure you geniuses are rare, and people of the same intelligence level tend to gather so you can go through a career completely missing them depending on where you work. There's enough noise such that among these groups you won't notice the correlation.

Tbh the geniuses don't view their own code as shitty, to them it's quality. It's only viewed as shitty externally.


I've come to the same conclusion, really smart people who write compilers, name their variables one single letter and the like (the extreme variant) etc., they can actually see the matrix beyond the funny characters on the screen, they have the capacity and attention to understand the messy bits without having to make it neat, those three nested for-loops inside multiple conditionals don't bother them, they can quickly visually parse difficult code without refactoring and sectioning it off. On the opposite side, there some like me who are deficient in that regard, so I spend my time pimping my code to look nice, nit-picking on syntax style, eliminating else-clauses, trivial stuff like that.


The discussion centers largely around the trade-off between program efficiency and developer productivity but one thing I thought that's missing that's also incredibly important is simply safety.

Today a lot of applications are concurrent, networked, distributed and built in ways where high level or performance impacting features make sense to make sure your program remains in a valid state.

Casey seems to take so much of his advice from his experience on game engines, which are an incredibly forgiving domain in many ways. You can have some bugs, you can drop some things over the network, it doesn't matter much. But you can't really do that in many other applications. If you had a big codebase for an application that is safety critical, going at it with his style of low-level programming you might make some very costly mistakes really quickly.


Casey isn’t arguing with hypotheticals. Right in the discussion they explore a perf bug in the GitHub UI. So don’t strawman by drawing borders around sensitive domains. Because clean code ideas are still producing buggy code.

(I am making an assumption that GitHub UI uses clean code ideas, but I feel comfortable doing that.)


No need to make assumptions given that they dug into that issue and the problem was algorithmic. As Bob points out that's orthogonal to clean code ideas. If you chose the wrong datastructure or algorithm and have quadratic complexity all the micro-optimizations in the world aren't going to help you.

The only strawman honestly is to pick someone at github writing shoddy code and then using that to argue against system architecture? It's pretty obvious that a text editor lagging on a paragraph of text isn't the consequnce of having wrapped something in a function. If you've written code slower than a human you don't have clean code, you just have bad code.


I wouldn't go down that line. There is code that makes you -think- it's safer but all it really does is add more needless complexity. It's one of those things where the 'safety' result hasn't really been given a good measurement.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: