Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • Why did Uncle Boob ever have any credence or credibility?

    What did Bob ever ship that gives him any gravitas or authority in this area?

  • I think the author comes from a very specific perspective; RAD tools, as I understand it, generally has only one, or a very few, software engineers per product. The way I would write code on a personal project is very different than the way I’d write code in an environment with changing team members, interns, guest commits, etc.

    Also, In real-time simulations (ie games) often then way you write code can be the bottleneck. In web services the bottlenecks are more often network calls, database model, etc.

  • I'm not sure I follow the thrust of the article. The author starts off with talking about clean code, but then compares OO with procedural code. It's not the same thing, and of course we've always known that OO abstractions carry a performance penalty. Even the founders of OO (Alan Kay et al.) acknowledged the memory and compute impact, but thought it was a worthwhile tradeoff for clean abstractions in complex code-bases.

    Back then computers were far less performant than they are today, so the first languages (e.g. SmallTalk) had to be compiled into a bytecode VM that ran on a Xerox PARC. Other efforts included hardcoding some of the constructs into the ISA.

  • He states at the start of the article the tenets of clean code he's arguing against, not just the general OOP of it. Shows how ignoring a certain tenet leads to increase performance, that's the thrust of the article. He routinely in the article goes back to the tenets he's arguing against.
  • It really depends on what you are building, coding is always about trade-offs, and sometimes (not always) you have to choose between maintainability/readability and performance.

    If you are writing code for embedded devices where every cpu cycle counts, I would indeed trade a bit of the maintenance for some cpu cycle.

    If I am writing a huge web app that has to be maintained years by a large team of devs, I would prefer a more simple/maintanable code over a fast one (+ in such scenarios the real bottlenecks are often your I/O, not the raw CPU perf).

    This is for the same reason you usually write code that needs to be fast in low-level programming lng like C and huge web app in Node.js or Java.

  • How much of the performance differences come down to language or compiler choice in these examples?

    Would I see the same kinds of performance gains or losses avoiding or using certain patterns in Go or Rust or Java? Are they the same examples as in C++?

    What about dynamic languages like ruby or python or javascript?

  • I believe in Rust there would be almost no performance hit due to the compiler using monomorphizing everything via the "zero-cost abstraction" we love to brag about
  • I'd say Clean Code is teaching many bad-practices. Too many to be recommended.
  • eh, when I read it as a newbie it was really helpful. still had to make my own experiences and judgments, but overall I think reading it made me a better programmer
  • > Functions should be small + Functions should do one thing

    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"

  • Yes. So all you need to do is look at the code samples.

    Is the most non-sensical thing I've seen. So of course the junior dev parade thinks it's the gospel.

    Had a terrible manger who would swear by this book but couldn't code his way out of a paper bag.

  • I think performance generally trades along a different axis: open-world vs closed-world assumptions. There are many cases where closed-world assumptions may confer performance benefits, such as tree-shaking, whole program optimization, and using switch statements rather than a class hierarchy. Whereas designing for extensibility necessarily precludes some of those choices (though it doesn’t necessarily require OOP, for example registering a handler in a table). In other words, it’s easier to optimize a problem that is fixed and well-understood, versus one flexible and unknown. Take that ideas to the extreme and end up at ASIC bitcoin miners.
  • It seems like the main takeaway is that many textbook OO paradigms aren't the most optimized representations of the code. In this case, the cost is dynamic dispatch and pointer-chasing. This is a function of the Shape abstraction, but not the abstraction itself.

    But the argument is you're trading some of that performance optimization for maintainability. None of this is exactly news. And while I'm here ranting: I never understood why shapes are the canonical OOP example. Shapes are a closed set of types (yes I'm sure GPT-324 invented a new one) with an open set of operations. There's always going to be one more thing you need to do with those shapes, but you'll never be adding new shapes down the road unless you are still in Kindergarten. OOP is useful for the exact opposite case, where there is a relatively fixed set of operations and you routinely introduce a new subtype that needs to perform all or most of those operations.

    I've noticed that most courses that introduce the concept of OOP do so in a way that (perhaps unintentionally) emphasizes the false notion that everything should have an 'x-is-a-y' taxonomy before actually asking the question if that is appropriate. Putting the Cart extends Vehicle before the Horse extends Animal.

  • the argument that it's more maintainable seems to never be grounded in much of anything though in my experience
  • I think single dynamic dispatch is one thing but ends up reasonably well optimised by modern compilers, especially the JVM. I think most code written since the 2010s prefers the composition over inheritance pattern for the most part so tends to use interfaces rather than concrete base classes.

    That said, double dispatch as in the visitor pattern is often too hard to analyse for optimisation and I think humans frequently get a bit lost with it as well. Fortunately pattern matching is doing away with it. I think it's one of these gang of four patterns that has a lot of people scratching their heads and wondering if the open/closed principal is that worth sticking to if this is the outcome.

  • > In this case, the cost is dynamic dispatch and pointer-chasing.

    To sharpen your statement, the cost is missing the CPU caches, which is often caused by failing to pool allocations and reading indirectly.

    > But the argument is you're trading some of that performance optimization for maintainability.

    Right, but exactly how much? I would argue "very OOP" design styles neuter your ability to optimize the system, and sometimes necessitate that you are kept at arms-length from the system, only capable of "customizing" it via more abstract API layers. I do believe certain OOP practices can make maintaining software easier, but I also believe we have not figured out how to retain control over the computer in the face of these abstractions.

    As an example, Clean Coders advocate for "separation of responsibilities" and often speak in terms like "ownership" or what a function/class "knows about" or "should have to know about." When different classes are given different data-fields in the pursuit of making it clearer (what should exist in that scope,) you are creating a constraint which is virally spread through the codebase which runs counter to what the CPU wants. The CPU wants an array, but you can't have an array because the FileManagerFile can't "know about" the FileManagerFileCache, and the FileManagerFileCache can't known about the FileCache, so now each FileManager "owns" its own cache, which is an entirely separate heap allocation.

  • Related:

    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

  • Ok now add a Path shape that has to calculate the area of a polygon with arbitrary complexity.

    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.

  • In that case, you'd branch into a separate function/block that runs the calculation. Sure, it's slower than a simple array index to find a coefficient, but you're only incurring that cost when you actually need it and it's still much faster than using polymorphism everywhere instead.
  • Yes, a toy problem only needs a simple implementation. This is a straw man. And I don't even like Robert Martin's Clean Code, but the author is not addressing where this style actually provides benefits. When you're updating 23 if-statements because you had to add support for some new business workflow, you'll wish you had a conceptual entity that encapsulated the operations on the type of workflows so you just had to implement them in one place.
  • the toy problem is for demonstration purposes. this is a lived experienced in everyday programming of performance sensitive fields.

    those apps tend to be vastly more architecturally complex in almost every way compared to your average corporate or web app too.

  • > When you're updating 23 if-statements because you had to add support for some new business workflow, you'll wish you had a conceptual entity that encapsulated the operations on the type of workflows so you just had to implement them in one place.

    Polymorphism won't get rid of the 23 if statements, it will just replace them with 23 method implementations. Then when you try to serialize that "conceptual entity" to a file or network socket you'll yearn for the if statements once more.

    The main benefit of polymorphism is that it allows you to modify one part of a program without recompiling the other parts. In the absence of pre-compiled modules, polymorphism is isomorphic to branching/switch statements:

    https://en.wikipedia.org/wiki/Expression_problem

  • This is the author's gripe, though. You're sacrificing end user experience for developer ergonomics.
  • > you'll wish you had a conceptual entity that encapsulated the operations on the type of workflows so you just had to implement them in one place.

    Why have you drawn the conclusion that the author is against this? A function with a switch-statement can do this.

  • Thank you! I always see this stupid conversation about performance and nobody seems to get this.
  • It's a problem chosen by the author of Clean Code. How is it a strawman? The author of the article is directly refuting the style of the problem/solution that the original author chose, and arguably demonstrated a better approach. That is not a strawman.
  • Indeed. The problem is trying to apply the principles of "Clean Code" or more generally of OOP everywhere. There are surely cases where having an interface as an abstraction and multiple implementations makes sense. There are case where it doesn't, and if you take as a dogma "everything shall be the implementation of an interface" you get more complex code and performance penalty for nothing.

    There is nothing wrong with having procedural code with a switch case, as there is nothing wrong in having global variables, in having even goto, depends on how you use it.

  • I consider Clean Code to be in the category of books/styles that is helpful for early developers who need some structure, but harmful to late-stage developers who adopt it as dogma.

    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.

  • Which would be fine if these people weren't the ones making / influencing the hiring decisions, paying them half a million dollars (even though they have done nothing) and people retweeted their “opinionated” takes on subjects they barely understand.
  • My personal benchmark for 'maybe this function is too long' is when it doesn't fit on the page.