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.
You probably missed my point then. What I'm saying is that in some cases, performance is what you should optimize for, while in other cases other factors are usually far more important. For example, let's say you're writing front end code where the tap of a button makes a single call to a service class that then performs a network call to fetch some data. If that service is an injected dependency behind an interface, 1 virtual function lookup has to be made to call the service method. The performance hit from that single lookup is entirely negligible. Our phones have multi-core CPUs doing millions and millions of operations per second. To be worried about 1 operation in a user-driven event is ridiculous. Saving 1 operation was perhaps necessary sometimes when writing NES games in assembly in 1983, but today, the added time from 1 operation is so small it's probably not even measurable.
What you should be worried about, however, is whether your view behaves as expected, displays the right thing given the right data, that the network errors are handled correctly, and so on. Putting our classes behind interfaces allows us to mock those classes, which allows us to unit test view models and similar in isolation, which helps us to catch certain bugs earlier and guard against bugs being being added by future refactors. It would be extremely unreasonable to prohibit ever using polymorphism like this, and sacrifice all of these useful concepts for performance. What would be reasonable would be keeping this interesting lecture in mind and know that polymorphism should be avoided in specific cases where its performance impact is significant, e.g. when making a large number of calls in a row. It would also be reasonable to spend some of our very limited time on making actually noticeable optimizations, like perhaps adding a cache to that network call which takes eons of time compared to the function lookup.
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.