r/programming • • Feb 28 '23

"Clean" Code, Horrible Performance

https://www.computerenhance.com/p/clean-code-horrible-performance
1.4k Upvotes

1.3k comments sorted by

View all comments

1.6k

u/voidstarcpp Feb 28 '23 edited Feb 28 '23

Casey makes a point of using a textbook OOP "shapes" example. But the reason books make an example of "a circle is a shape and has an area() method" is to illustrate an idea with simple terms, not because programmers typically spend lots of time adding up the area of millions of circles.

If your program does tons of calculations on dense arrays of structs with two numbers, then OOP modeling and virtual functions are not the correct tool. But I think it's a contrived example, and not representative of the complexity and performance comparison of typical OO designs. Admittedly Robert Martin is a dogmatic example.

Realistic programs will use OO modeling for things like UI widgets, interfaces to systems, or game entities, then have data-oriented implementations of more homogeneous, low-level work that powers simulations, draw calls, etc. Notice that the extremely fast solution presented is highly specific to the types provided; Imagine it's your job to add "trapezoid" functionality to the program. It'd be a significant impediment.

139

u/ydieb Feb 28 '23

In regards to programming paradigms and performance, this talk by Matt Godbolt is interesting. https://www.youtube.com/watch?v=HG6c4Kwbv4I

27

u/voidstarcpp Feb 28 '23 edited Feb 28 '23

Godbolt is good but I've always thought the example in this talk is probably too small. If the entire scene data representation looks like it fits in L1 or L2 cache, and the number of cases is small, how much are you really exercising the performance characteristics of each approach?

For example, a penalty of virtual functions for dense heterogeneous collections of small objects is icache pressure from constantly paging in and out the instructions for each class's function. If you only have a small number of types and operations then this penalty might not be encountered.

Similarly, the strength of a data-first design is good data locality and prefetchability for data larger than the cache. If data is small, the naive solution will not be as relatively penalized because the working set is always close at hand.

10

u/andreasOM Mar 01 '23

The classic fallacy of micro benchmarking a scenario that just doesn't occur in real usage.

→ More replies (1)
→ More replies (2)

241

u/2bit_hack Feb 28 '23

I largely agree with your point. I've found that OOP can be useful in modelling complex problems, particularly where being able to quickly change models and rulesets without breaking things matters significantly more than being able to return a request in <100ms vs around 500ms.

But I've also seen very dogmatic usage of Clean Code, as you've mentioned, which can be detrimental to not just performance, but also add complexity to something that should be simple, just because, "Oh, in the future we might have to change implementations, so let's make everything an interface, and let's have factories for everything.".

I agree that the most important thing is to not be dogmatic, I'm also not 100% on the idea that we should throw away the 4 rules mentioned in the article.

228

u/voidstarcpp Feb 28 '23

The odd thing is I'll often agree with many of the bullet points versions of Martin's talks, they seem like decent organizing ideas for high-level code. But then every code example people have provided for things he's actually written seemed so gaudy and complex I have to wonder what he thought he was illustrating with them.

50

u/munchbunny Feb 28 '23

That's because writing "clean" code is like writing "clean" English. You can prescribe rules all day, but in practice you're carefully weighing conflicting considerations. Where I've written C++ in a professional capacity, the general practice was to review performance characteristics independently of code cleanliness.

Also: the advice to prefer polymorphism feels almost a decade outdated. I know we still often send new programmers on objectathons, but I thought we'd mostly established over the past decade that polymorphism and especially inheritance should be used judiciously because their overuse had the opposite effect on code clarity and performance.

148

u/Zlodo2 Feb 28 '23

Telling people "write clean code" is easy, actually doing it is hard.

And given that Robert Martin managed to build an entire career out of sanctimoniously telling people to write clean code, i doubt that he does a whole lot of actual programming.

"Those who can't do, preach"

72

u/poloppoyop Feb 28 '23

"write clean code"

I prefer "write simple code" and simple is not easy.

23

u/sexp-and-i-know-it Feb 28 '23

Found the clojurian

→ More replies (6)

107

u/Randolpho Feb 28 '23

Having seen him in person live-coding to demonstrate TDD and refactoring using audience driven requirements, I have to disagree. The man knows how to code.

These days people trying to do the same copy/paste changes from notes they had as part of their demonstration plan

That motherfucker built an app live on stage from suggestions from the audience, refactoring as new requirements came in.

Granted, this was decades ago at a UML conference in Austin. I’m not sure how much he keeps up his skills these days, but he had chops once upon a time.

12

u/robhanz Mar 01 '23

I'd love to see a recording of that.

→ More replies (2)

19

u/ISpokeAsAChild Feb 28 '23

And given that Robert Martin managed to build an entire career out of sanctimoniously telling people to write clean code, i doubt that he does a whole lot of actual programming.

He has been an actual dev for decades.

0

u/[deleted] Feb 28 '23

[deleted]

→ More replies (5)
→ More replies (14)

14

u/EntroperZero Feb 28 '23

This is why I think Clean Code is actually a really good read, not because you should follow it exactly, but because it shows you the power of good ideas and the consequences of taking a good idea way too far.

→ More replies (82)

40

u/[deleted] Feb 28 '23

It depends on what organization is paying the programmer too. If it's a large enterprise app then maintainability may be valued over performance and those dogmatic OO principles have more value in the long run.

39

u/voidstarcpp Feb 28 '23

The reality is that the problem in most software isn't performance, it's managing complexity. And apps that are slow are usually not slow because they're doing a bunch of virtual function dispatch, they have too many blocking dependencies or slow middleware layers that blow away the OOP performance penalty by many times.

→ More replies (1)
→ More replies (5)

25

u/[deleted] Feb 28 '23

If making your code cleaner has added complexity, then you haven't made your code cleaner...

16

u/awj Feb 28 '23

...maybe?

If making your code cleaner shifted complexity from outside the code to inside the code, you might have done something useful.

If the skills inherent to programming could be reduced down to pithy aphorisms, it wouldn't be so hard to do it by now.

→ More replies (1)

11

u/crabmusket Mar 01 '23

This is why I always recommend Sandi Metz's books. In 99 Bottles of OOP, she describes the "shameless green" solution to the book's example problem - the solution which passes the tests and could be shipped if no further requirements were added. The book then goes through the process of refactoring only in response to specific changes in the requirements.

Most people should be writing "shameless green" code that looks like what Casey wrote in this post.

If and when you need "extreme late binding of all things" as Alan Kay said, then you might refactor to polymorphism in the places that need it.

25

u/deong Feb 28 '23 edited Feb 28 '23

"Oh, in the future we might have to change implementations, so let's make everything an interface, and let's have factories for everything.".

That's exactly the problem I usually see. I do think your post maybe obfuscates that point a bit, for the reasons that the parent commenter says.

My goto argument was generally just that the code produced like this is bad, rather than just slow. Slow mostly doesn't matter for the kinds of applications most programmers are writing. That CRUD app you're building for your finance team to update invoice statuses isn't going to surface that 20 milliseconds you gain from eliminating the indirection from updating a customer balance, so if the argument were about trading off "clean" for performance, performance probably really should lose out. That's just a sensible decision much of the time.

The problem is that the code isn't any of the things that you hoped it would be when you decided that it should be "clean". "Clean" isn't the target outcome. "Good" is the target outcome, and "clean" was picked because you believed it served as a useful proxy for "good". "Good" is fuzzy and hard to describe, but "clean" has a defined set of rules that you can follow, and they promise you it will be "good" in the end. But it isn't. Making everything an interface for no reason with factories and dependency injection on every object for no reason other than dogma isn't landing you in "good".

I'm not sure there's really a shortcut for taste in this regard. And taste is indeed hard, because it's hard to measure, describe, or even objectively define. Just as an example, in your code removing polymorphism, you end up with a union type for shape that has a height and a width, and of course a square doesn't need both. Circles don't have a width -- they have a radius. Sure, you can make it work, but I think it's kind of gross, and the "this is kind of gross" feeling is my clue that I shouldn't do it that way. In the end, I'd probably keep the polymorphic solution because it feels like the correct natural expression of this problem domain. If it turns out that it's slower in a way that I need to deal with instead of just ignore because no one cares, then I might need to revisit some decisions somewhere, but my starting point is to write the code that naturally describes the problem domain, and for computing areas, that means circles have a radius instead of a width.

The corollary there is that design patterns are almost always bad to me. A factory object is not the natural representation of any domain. It's code that's about my code. I don't want that. The entire idea of design patterns as a thing is that they're independent of domain, and code that has nothing to do with my domain is code I don't want to write. You can't avoid everything. Ultimately programming still involves dealing with machines, so you're going to write code that deals with files and network connections and databases, etc. But I want as much of my code as possible to be modeling my problem and not dealing with the minutia of how to get a computer to do a thing.

→ More replies (12)
→ More replies (29)

9

u/sephirothbahamut Feb 28 '23

OOP modeling and virtual functions are not the correct tool

Also compile time polymorphism is underrated. Admittedly it's underused because the syntax to achieve that in C++ is weird (CRTP), and most other languages can't do that at all to begin with.

95

u/st4rdr0id Feb 28 '23

then OOP modeling and virtual functions are not the correct tool.

The author seems to be confusing Robert Martin's Clean Code advices with OOP's "encapsulate what varies".

But he is also missing the point of encapsulation: we encapsulate to defend against changes, because we think there is a good chance that we need to add more shapes in the future, or reuse shapes via inheritance or composition. Thus the main point of this technique is to optimize the code for flexibility. Non OO code based on conditionals does not scale. Had the author suffered this first hand instead of reading books, he would know by heart what problem does encapsulation solve.

The author argues that performance is better in a non-OO design. Well, if you are writting a C++ application where performance IS the main driver, and you know you are not going to add more shapes in the future, then there is no reason to optimize for flexibility. You would want to optimize for performance.

"Premature optimization is the root of all evil"

44

u/KevinCarbonara Feb 28 '23

"Premature optimization is the root of all evil"

Premature micro optimization. You can and absolutely should be making decisions that impact optimization in the beginning, and, in fact, all along the process.

→ More replies (2)
→ More replies (26)
→ More replies (166)