“Clean Code, Horrible Performance” Discussion(github.com) |
“Clean Code, Horrible Performance” Discussion(github.com) |
> 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.
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.
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.
> 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.
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.
that way of thinking is part of the problem.
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.
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.?
Why digging at ancient books? Is there something going on here?
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.
Otherwise you can only struggle to add as little dirt as possible.
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.
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?
Tbh the geniuses don't view their own code as shitty, to them it's quality. It's only viewed as shitty externally.
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.
(I am making an assumption that GitHub UI uses clean code ideas, but I feel comfortable doing that.)
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.
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.
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.
https://nigeltao.github.io/blog/2021/json-with-commas-commen...
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.
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.
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.
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).
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.
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.
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.
Here are my thoughts in a bit more detail from a previous thread: https://news.ycombinator.com/item?id=35061151
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.
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.
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.
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.
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.
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!
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.
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.
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.
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.
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.
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.
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.
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 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”.
That is a giant presumption.
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
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.
Make it run.
Make it clean. (and if need be,)
Make it fast.
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.
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.
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.
“Clean” code, horrible performance - https://news.ycombinator.com/item?id=34966137 - Feb 2023 (906 comments)
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...
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.
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.
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.
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.
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.
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.
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!)
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.
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.
And then he goes on touting readability right after that. Bold.
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.
> 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.
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.
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.
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.
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.
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 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.
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.
Which is a large part of what makes that attribution amusing.
I fully agree that human comprehension should be a prime objective for code. Alas, following the tenets of “Clean Code” produces anything but that.
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...
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.
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.
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.
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.
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.
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.
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.
This is a great comment. Quotable even.
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.
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.
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.
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.
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".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."
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.
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.
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?
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?
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.
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.
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.
"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.
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.
> 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 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.
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.)
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.
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)
What a great book. I second this and add my own "The Pragmatic Programmer: 20th anniversary edition"
That, and "Team Geek", in term of relationship with codes and teammates.
edit: oh, and also it's hard to avoid people checking in and/or editing the generated code
For those wondering, "How to Design Programs"
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?
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).
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!
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.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.
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.
Nothing to do with "just changing code because they feel like it".
There is nothing that makes good code more or less likely to change than bad code.
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.
That was a beautiful rant :)
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.
A struct only contains data, however you e.g. can define methods on it or implement traits for it No inheritance.