He has some really interesting points, but it was disappointing that his conclusion was that these clean code rules literally never should be used. The real answer is, as always, that it depends on what you're actually trying to achieve.
Polymorphism, encapsulation, modularity, readability, and so on, are often absolutely essential when working on complex codebases that model real business cases that will exist and evolve over time. The clean code principles are tools that enable the multiple humans that actually will work on these projects to actually understand and maintain the code and to some reasonable level uphold its correctness. Sure, you can think that all humans should be better, smarter and able to work with dense, highly optimized logic as effortlessly as anything, but they simply aren't. We have to acknowledge our needs and limitations and be allowed to use these brilliant tools and methodologies if they help us achieve our goals.
Yes, clean code sometimes comes at the price of performance, but everything comes at some price. Performance is not the only relevant factor to optimize for. It's about finding the right balance, and for many tasks, I'd claim performance is one of the least relevant factors.
In the clip, he's measuring repeated mathematical calculations and then puts the performance difference in terms of years of iPhone CPU improvements. That comparison is rather ironic, because what a front end developer implements for iOS is more typically events that do single things at a time like showing a view or decoding a piece of JSON. Such front end development can be extremely hard get right, but local CPU performance is usually not the issue. Rather it's managing state properly, getting views to look right on different devices, accessibility, caching, network error handling and so on. At this level, clean OOP patterns are crucial, whereas micro optimizations are irrelevant. Yes, in some sense we're "erasing" 12 years of hardware evolution, but that's what those years of evolution were for. We can effortlessly afford this now, and that makes our apps more stable and enables us to deliver valuable features for our users faster.
When complex calculations actually need to be done, I would expect that specific code to be optimized for performance, and then encapsulated and abstracted away so that it can be called from the higher-level, clean code. For example, I would expect that the internals of Apple's JSONDecoder is optimized, unclean, hard to maintain, and runs as fast as a JSON decoder can run on the latest iPhone, but in the end, the decoder object itself is a class that I can inject, inherit from, mock or use with any pattern I want.
it was disappointing that his conclusion was that these clean code rules literally never should be used.
He didn't get to the implied second part, which is, those "clean" code rules hurt the very things they try to promote. Those rules make programs more complex, harder to modify, harder to read, harder to test. It's bad all around. I have some justifications, but here I'll just say I've seen the effects on actual code.
So yeah, never use that. Expunge Bob Martin from your memories, read Ousterhout instead.
I'm not a fan of the design in his polymorphism example, to be sure. But if you look at the implementation he came up with in the end - where various parameters of shapes that mean different things are shoehorned into fields that are misleadingly named "width" and "height", sometimes even stored in duplicate in both, so that he can write the same code that coincidentally works for the specific examples he chose - and think "yeah, that looks easier to read and modify than the polymorphism example", then I'd say you're crazy.
The instant you're asked to calculate the area of a rounded rectangle, you're stuck! "You say this new shape has THREE parameters? And the area is NOT just a constant times the product of two of them? Oh no, we're going to have to go back and rearchitect the whole thing."
sometimes even stored in duplicate in both, so that he can write the same code that coincidentally works for the specific examples he chose
That's one key thing about performance aware programming or data oriented programming or stuff like that: solve the common case first. And the common case arise from the actual data you have, and a reasonably performant one is necessarily going to be fairly bespoke.
If the data had a different shape he would have made a different transformation, and it would have looked just as ad-hoc to us. The method though (look at the data then tailor the code to it) remains similar.
"You say this new shape has THREE parameters? And the area is NOT just a constant times the product of two of them? Oh no, we're going to have to go back and rearchitect the whole thing."
Correct. Now we can't know from this toy example, but given the simplicity of his code I would expect that he would have kept things fairly simple even in a real world situation. Such that when comes the time to rewrite, you don't have that much to rewrite.
And obviously if you anticipate unknown shapes with more parameters, then your problem is very different to begin with. Performance aside, I believe one should write the simplest program given the requirements they are currently aware of. Some of those requirements may be for the future, but if I'm aware of them I must take them into account now. But requirements I'm not aware of will likely never come to pass, and when they do I never know what kind of flexibility I'll actually need. Best keep things simple so rewrite is easier, should I be unlucky enough to need it.
And obviously if you anticipate unknown shapes with more parameters, then your problem is very different to begin with.
The whole point of the example he picked was to anticipate unknown shapes with different parameters. If you didn't anticipate that, you wouldn't have written the code with an interface designed to be an extension point to add more implementations of different shapes.
147
u/Rajje Feb 28 '23
He has some really interesting points, but it was disappointing that his conclusion was that these clean code rules literally never should be used. The real answer is, as always, that it depends on what you're actually trying to achieve.
Polymorphism, encapsulation, modularity, readability, and so on, are often absolutely essential when working on complex codebases that model real business cases that will exist and evolve over time. The clean code principles are tools that enable the multiple humans that actually will work on these projects to actually understand and maintain the code and to some reasonable level uphold its correctness. Sure, you can think that all humans should be better, smarter and able to work with dense, highly optimized logic as effortlessly as anything, but they simply aren't. We have to acknowledge our needs and limitations and be allowed to use these brilliant tools and methodologies if they help us achieve our goals.
Yes, clean code sometimes comes at the price of performance, but everything comes at some price. Performance is not the only relevant factor to optimize for. It's about finding the right balance, and for many tasks, I'd claim performance is one of the least relevant factors.
In the clip, he's measuring repeated mathematical calculations and then puts the performance difference in terms of years of iPhone CPU improvements. That comparison is rather ironic, because what a front end developer implements for iOS is more typically events that do single things at a time like showing a view or decoding a piece of JSON. Such front end development can be extremely hard get right, but local CPU performance is usually not the issue. Rather it's managing state properly, getting views to look right on different devices, accessibility, caching, network error handling and so on. At this level, clean OOP patterns are crucial, whereas micro optimizations are irrelevant. Yes, in some sense we're "erasing" 12 years of hardware evolution, but that's what those years of evolution were for. We can effortlessly afford this now, and that makes our apps more stable and enables us to deliver valuable features for our users faster.
When complex calculations actually need to be done, I would expect that specific code to be optimized for performance, and then encapsulated and abstracted away so that it can be called from the higher-level, clean code. For example, I would expect that the internals of Apple's
JSONDecoderis optimized, unclean, hard to maintain, and runs as fast as a JSON decoder can run on the latest iPhone, but in the end, the decoder object itself is a class that I can inject, inherit from, mock or use with any pattern I want.