HI version is available. Content is displayed in original English for accuracy.
Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
48% Positive
Analyzed from 1495 words in the discussion.
Trending Topics
#code#clean#polymorphism#performance#languages#don#should#type#casey#runtime

Discussion (24 Comments)Read Original on HackerNews
On a long enough career path, eventually you will run into one Clean Code zealot who carries an air of superiority and nit picks every PR over things like a function having more than an arbitrary number of lines in it instead of reviewing the actual code. This is the point where most people come to hate Clean Code.
As much as it pains you, that's exactly the feedback that should be provided to developers such as yourself, specially if you do not understand why it matters.
Unmaintainable code is prevented at the PR stage. The likes of you need this feedback because you aren't mindful to the problems it reflects.
The problem with a long function is not if it has N or N+1 lines of code, it's that length code with many branching conditions is prone to be untestable and introduce non trivial bugs. Once you start to refactor, not only is it easier to parse but harder to break. This fact is known for decades now.
Your comment is really not about clean code, it reads like frustration for having team members point out the quality issues in the code you're delivering and your unwillingness or inability to understand why it's a problem and why you should correct your approach.
There is a class of developers who are very vocal against established practices, such as OO, clean code, TDD, Etc. but ultimately when their complains are hold to scrutiny it's evident that the issue doesn't lie with OO, clean code, TDD, etc.
If you don't use those rules, you'll argue about something else in the code reviews. Likely something even more ambigous that wasn't explicitly written down for everyone as a baseline.
Consider how the workload is now dominated by the core task of actually calculating the area, reducing the impact of struct usage.
Consider the diffs required to make this change.
It's not like Clean Code should be taken as gospel but this micro-benchmark is not a realistic example of what CC is trying to solve.
Occam's razor applies to all domains. Don't use confusing implementations until there are no good options left.
HN post for original article on 2023-02-28 (https://news.ycombinator.com/item?id=34966137), 739 points, 914 comments
Discussion between Casey (author of this article) and Uncle Bob (author of _Clean Code_, whose programming patterns Casey is critiquing), posted on HN on 2023-03-11 (https://news.ycombinator.com/item?id=35105528), 223 points, 213 comments
"Horrible Code, Clean Performance", a "homage" to Casey's original article, posted on HN on 2023-04-19 (https://news.ycombinator.com/item?id=35596069), 121 points, 114 comments
This is often a trap for performance. Sure, it looks nice on a screen but calling a function to return a variable is usually epic waste of performance unless compiler will save you by inlining the function into your code or architecture you are using has a magic instruction for that (call vs fcall - which compiler has to recognize and use) which is just fancy "goto there, mov r1 <- *var, goto back"
programming in general is waaaaaay bigger than what the book covers.
It's way bigger than any book covers. Clean Code has some useful things, but if anyone actually reads chapter 1 they'd see that Martin even addresses the idea that you should not just read Clean Code and use it alone, or even entirely. It's a collection of one person's judgements (some good, some bad), just like all the other books like it.
Whenever I run a thing and it's unbearabily super duper slow, when you look at the process lists the thing will have spawned bunch of chromium instances - on top of probably making bunch of internet connections. Delegating some of the work that can easily done on my PC to "cloud" instead.
What we have now is way worse - it's electron and webshit technologies on desktop. Like you couldn't make software of worse quality even if you tried. The performance way worse than PCs of 1990s. It's almost like using software that's running from a floppy disk.
And now this trash is probably getting generated with LLMs.
This sounds like tackling the problems of C++ in the early 2000s.
1. Casey Muratori also that DRY shouldn't doesn't have to result in non-performant code.
2. Smaller functions, functions that do one-thing: Modern compiler can inline those. There are some edge cases where inlining may make less efficient use of states and loops but I don't think that's a main problem nowadays. I also wouldn't say the extreme version of this idea (very small functions) is still popular. The strongest proponent of this was Uncle Bob, and the last time I've heard him speak about code, he said he now lets the LLM write everything and he only reviews the module hierarchy and maybe the modules' public interfaces.
3. Polymorphism instead of ifs and switches was a big fad in the late 1990s until the late 2000s and had some holdouts in the 2010s. It was only ever popular in the Enterprise Java and C++ world (and maybe in Enterprise Smalltalk, never hard). Overuse of runtime polymorphism widely considered bad form in newer static languages like Go and Rust and in most dynamic languages there was always a tacit understanding of "use mostly conditions, add polymorphism if you need extensibility".
In functional languages (or languages heavily influenced by functional programming like Rust, Swift and Kotlin[1]), the classic approach for the type of scenario in this example is to use a sum type, and run a safe exhaustive match/switch on all the variants.
4. Hiding internals: The sum type example is telling of modern best-practices. Sum type fields are generally made public. Some languages (e.g. Rust and most pure functional languages) do not support private fields in sum types at all! Other languages (e.g. Kotlin) but immutable, so it's easy to maintain invariants without hiding information. Sometimes we do want to hide the type details and wrap it with public-facing type (this is a common pattern with internal error enums in Rust for example). Even in this case, there is no impact since we do not use runtime polymorphism or indirection (that would be Box<T> in Rust).
Due to compiler optimizations, hiding internals has marginal performance cost (if any) unless you require runtime polymorphism to achieve it. But why should you?
I feel like the performance costs lamented in this article mostly have to do with runtime polymorphism in static languages. And I fully agree here: runtime polymorphism is something that should be avoided when you don't need it[2]. But that's the thing: if you're looking at modern static language codebases, runtime polymorphism is not as hyped as it used to be in the past. Some languages still require heavy use of runtime polymorphism (Go is a good example of this), but other languages more often rely on static polymorphism (Rust) or compile time duck-typing (Zig and you could argue C++ template meta-programming used to do that, albeit quite awkwardly).
Even with all the issues you get with polymorphism, I don't think it's the main cause of slow application performance. It be very much the culprit in tight loops inside games, but if you look at the performance issues plaguing everyday apps, I think the two major culprits are endless layers of abstraction (the most quintessential example is basically every sluggish Electron app out there) and blocking the user on slow actions (like network loads).
---
[1] Even Java had sealed record types for a while now, and I'm sure will see Enterprise frameworks encouraging them in 20 years, when the rest of the world has moved on to spacefaring super-intelligent LLMs. But Enterprise frameworks also don't encourage you to write DRY code or keep your functions short.
[2] But do keep in mind that in Java it could be almost zero-cost in many cases. The JIT will monomorphize or bimorphize your classes if you always use the same class at the same callsite. The pointer indirection is not an extra cost, since every non-primitive that doesn't undergo Scalar Replacement[3] lives on the heap, and has a pointer.
[3] https://shipilev.net/jvm/anatomy-quarks/18-scalar-replacemen...