The tests he put together here are hardly something I'd call a straw-man argument, they seem like reasonable simplification of real-cases.
The focus on performance here ignores the fact that most programs are large systems of many things that interact with each other. That is where good design and abstractions and “clean code” can really help.
Like all things it is about finding a balance and applying the right techniques to the right parts of a larger system.
To me the interesting point is the reminder that there is an innate tension between going fast and being "clean" (i.e. maintanable/understandable). And once you are aware of it you can make your decisions in an informed way. Too often this tension is forgotten/ignored/dogmatically put to the back ("performance doesn't matter over cleanliness" and the likes).
Mind you, I'm also of the camp that performance is very secondary to cleanliness in modern enterprises, but I appreciate a reminder of just how much we are sacrificing on this altar.
It is not an excellent argument for why 'clean code' is not a better fit for the other 99% of their codebase.
For example, if the code is to be distributed and extended as a third-party library, then the class hierarchy is probably a better fit to allow extensibility.
But if the purpose of the API is to compute the area of given shapes (as in the example), then it makes sense to make it efficient and there is no use to provide extensibility to the outside world.
The advocates of "clean code" that Casey mentions will go for extensibility no matter the use.
I find it very interesting to have numbers to weight what you are leaving when you go the extensibility route instead of the non-pessimistic route.
Unfortunately this is just some catchy stereotyping that probably doesn't match reality.
The process was "think about the model, design a class hierarchy that fits with the model, add the operations". Even if I don't think about extensibility, that's how I thought my code.
Why? Because when I think about performance, I used to think about algorithms and I/O optimizations (for example batching).
Now I'll look into this data-oriented programming-thing and see how that applies to my platform (embedded Java) :D
That's a fairly rare breed for sure, but the real problem was that nobody called him out. Well I did, but I'm no longer working there. I couldn't.
Paraphrasing Russ Ackoff, doing the right thing and doing a thing right is the difference between wisdom and efficiency. What Casey is doing here may be efficient, but calculating a billion rectangles doesn't present a realistic or general use case.
"Clean Code" or any paradigm of the sort aims to make qualitative, not quantitative improvements to code. What you gain isn't performance but clarity when you build large systems, reduction in errors, reduce complexity, and so on. Nobody denies that you can make a program faster by manually squeezing performance out if it. But that isn't the only thing that matters, even if it's something you can easily benchmark.
Looking at a tiny code example tells you very little about the consequences of programming like this. If we program with disregard for the system in favour of performance of one of its parts, what does that mean three years down the line, in a codebase with millions of lines of code?`That's just one question.
You should check if your code is in the hot path before optimizing, because the more you couple things together the harder it is to change it around. For instance, in Casey's example, if you wanted to add a polygon shape but you've optimized calculating area into multiplying height x width by a coefficient, that requires a significant refactor. If you are sure you don't need polygons, that's a perfectly fine optimization. But if you do, you need to start deoptimizing.
There's lots of ways to do this poorly and well. There's no process for it. That's a feature. I feel like a lot of the flak clean code gets boils down to, "I followed it dogmatically and look what it made me do!" It didn't make you do anything; it's trying to teach you aesthetics, not a process. Internalize the aesthetics and you won't need a rigid process.
Obviously when you do this you probably need more code than you'd normally write. That can be viewed as a maintenance burden in some situations, esp. when you don't have product market fit. Again, this shows that treating clean code like some process that always produces better code in every situation is extremely naive.
Also I think he isn't specifically against "clean code" but how the first tool used by various "clean code advocates" seems to be polymorphism. I have seen this enough in lots of Java codebases and "Enterprise C++ codebases". "We need X." "Oh first I will create an Abstract Base class for X, then create X, so we can reuse X nicely elsewhere when we need it". It is still on the developers to understand that they may not even need it but for them it is "clean code".
The abstraction is super common and allows you to connect streams to each other without worrying about the underlying mechanism, which 99% of the time I don't really think you want to worry about unless you're sure it's a performance overhead. And that's great, because I surely don't want to write specializations by hand for all the different combinations of streams I need to use if I don't have to.
I use generic readers and writers every day, but they don't have any runtime dispatch. The question was "Why would you do anything except vtables and runtime dispatch?" One answer is that my code that uses generic readers and writers and gets monomorphized gets to also be generic over async-ness.
virtual void setPixel(int x, int y, int color) = 0;
and then implemented flood fill etc in terms of that.You can still easily get something working just by implementing setPixel without virtual dispatch, the linker has no problem inlining that call at compile time.
If some arbitrary API needs it to be virtual it's easy to implement the virtual call in just that specific case, instead of burdening your entire system with a virtual call that'll always be static in practice.
Your entire system will not be burdened with virtual calls in places where you use concrete implementations (so long as they're final/sealed, anyway). The overhead is only there if you try to use the abstraction generically, but why would you do that in the case where the virtual call "will always be static in practice"?
It's only a problem when it's done per datum.
Heck, even codebases used in trading can use polymorphism for very high level interfaces (or async tasks) but hot code paths don't use it.
Clean code means easy to read, maintain, not full of arbitrary things 'because performance'.
There was a moment in grade school where I was sat down and it was explained to me that you don't have to take a test in order. You can skip around if you want to, and I ran so far with that notion that at 25 I probably should have written a book on how to take tests, while I could still remember most of it.
One of the few other "lightning bolt out of the blue" experiences I can recall was realizing that some code constructs are easier for both the human and the compiler to understand. You can by sympathetic to both instead of compromising. They both have a fundamental problem around how many concepts they can juggle at the exact same time. For any given commit message or PR you can adjust your reasoning for whichever stick the reviewer has up their butt about justifying code changes.
you have Japan infrastructure, and you have Turkey infrastructure
6.1 quake in Japan = nothing destroyed
6.1 quake in Turkey = everything collapses
The engineers in Turkey probably didn't value performance and efficiency
It's the same for developers, you choose your camp wisely, otherwise people will complain at you if they can no longer bear your choice
You act like innocent, but your code choice translate to a cost (higher server bill for your company, higher energy bill for your customers/users, time wasted for everyone, depleting rare materials at a faster rate, growing tech junk)
Selfishness is high in the software industry
We are lucky it's not the same for the HW industry, but it's getting hard for them to hide your incompetence, as more things now run on a battery, and the battery tech is kinda struggling
Good thing is they get to sell more HW since the CPU is "becoming slower" lol
So we now got smartwatches that one need to recharge every damn day
Uh, no. It was corruption. They were standards, that worked, but people didn't do it, plain and simple.
A "blame systems over individuals" version would be that the industry is externalizing bad performance onto users, damaging environment, causing frustration, wasting lives, and occasionally even actually killing people (shitty ER/hospital software comes to mind) - because there's no good feedback mechanism to force software companies to internalize those costs.
I agree, i apologies for the mistake, i can no longer edit the post unfortunately
The reality is that how flexible your interfaces and abstractions are and their design has to be a part of your original design considerations when building something. It's a bad move to just hand wave away performance concerns because you religiously adhere to some design patterns. It's also a bad move to drop down to using intrinsics for everything from the get-go and thinking you know better than the compiler when it's a codepath that isn't even computationally expensive or a bottleneck a priori.
We can all write code that glues a very fixed set of things end to end and squeeze every last CPU cycle of performance out of it, but as we all know, software requirements change, and things like polymorphism allow for much better composition of functionality.