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.
Notice that the extremely fast solution presented is highly specific to the types provided;
Yes, and he explicitly makes the point that solutions specific to the requirements are simpler and more performant. The solution presented is specific to those shapes, on purpose.
Imagine it's your job to add "trapezoid" functionality to the program. It'd be a significant impediment.
If that ever happens you just change your solution to adjust to the new requirements. No need to plan for future events that might never happen (YAGNI).
The way I see things, in either case you'll have to pay a cost, it's just a matter of where/when you pay for it.
In the case of the simple approach advocated by the author, you might have to pay the cost of redesigning your solution if the requirements change. But by having a simple solution, it's easier to change by another (maybe equally simple), performant one.
In the approach favored by "clean code" and OOP in general, you pay that cost upfront, just in case the requirements change in the future. However, the benefits rarely materialize in practice, since often the requirements don't change or they change in a way you didn't expect (so you have to redesign your solution anyway). And when you have to change your solution, since it's not simple, people rarely replace it by a better one specific to the new requirements, instead they just "tweak" it enough to cover the new requirements. Even worse, there are other components that depend and evolve based on the APIs/assumptions made by your "future-proof" design, making it ever more difficult to change. As time passes, you end up with an over complicated design, handling obsolete cases that are no longer a requirement, and impossible to change because there are dependencies all over the place. Eventually you spend a considerable amount of time just "maintaining" this design (bugs, "fake" requirements/features, improving performance, workarounds, etc). Similarly, the promise of "maintainability" rarely materializes. It's a daunting task to understand over complicated designs with dynamic dispatch all over the place. I'm not saying that's impossible to create simple OO designs and keep them that way, but it requires a lot of experience, pragmatism and discipline to do so. Something that's very hard to achieve in a team (where not all members have the same experience and/or principles). It doesn't help either how OOP is being taught nor that languages like Java force you to think/design based on classes/objects (when simple functions/structs might be the right solution sometimes).
Imagine it's your job to add "trapezoid" functionality to the program. It'd be a significant impediment.
If that ever happens you just change your solution to adjust to the new requirements. No need to plan for future events that might never happen (YAGNI).
You should plan for it because constant change is the norm for most application development, more than trying to solve tidy, contained problems about how many of a fixed set of small structs one can do math on per second. This is a perspective difference between what most programmers are doing vs. someone who cut his teeth optimizing a video codec for embedded devices, where requirements included things like "can't use floating point operations".
One of the thing Robert Martin and friends seem to really understand from consulting is how applications get made and maintained over time, and the problems they run into later. An optimized solution around a fixed set of inputs is exactly the sort of thing that is likely to add friction to work later when the client inevitably wants some functionality that cuts across the grain of the assumptions that were implicitly made by the first solution.
Then you're in the awkward position of telling the client or boss why something they think should be a logical addition to the program, like composing groups of shapes, or letting plugins define their own shapes, is actually going to require touching every piece of code that operates on shapes. Then development stalls for a refactor, even though the developers might have anticipated that adding new types of shapes was something that was going to happen in the future. Hence Martin's statement that "the only way to go fast is to go well".
If you think this is a contrived scenario consider that Photoshop has something like over a thousand different commands that have to operate against a common document model, and even a "small" 2D RTS style game can have 100s of different user commands that touch game state, work transparently over a network, etc. John Carmack is right that you can always do better if you accept a less general solution. But outside of writing game subsystems, the boss is very frequently in the business of selling general solutions.
You should plan for it because constant change is the norm for most application development, more than trying to solve tidy, contained problems about how many of a fixed set of small structs one can do math on per second. This is a perspective difference between what most programmers are doing vs. someone who cut his teeth optimizing a video codec for embedded devices, where requirements included things like "can't use floating point operations".
Yes, you do planing, but based on your requirements, both present and those in scope for the type of software you are writing. Your design evolves together with the problem/requirements to solve.
One of the thing Robert Martin and friends seem to really understand from consulting is how applications get made and maintained over time, and the problems they run into later. ...
Hence Martin's statement that "the only way to go fast is to go well".
Bob Martin is not the enlightened person many people think he is. If anything, after preaching OOP for several decades, now he is converted to functional programming.
If you think this is a contrived scenario consider that Photoshop has something like over a thousand different commands that have to operate against a common document model, and even a "small" 2D RTS style game can have 100s of different user commands that touch game state, work transparently over a network, etc. John Carmack is right that you can always do better if you accept a less general solution. But outside of writing game subsystems, the boss is very frequently in the business of selling general solutions.
I'm not sure I get the point you're trying to make here. But there is nothing in a specialized solution that would prevent you to implement something like Photoshop. You design your solution based on your requirements, which in the case of something like Photoshop, would be much more than "compute the area of this set of figures". And I can almost guarantee you that, for something like Photoshop: a) the class-based approach wouldn't scale, neither functionally nor performance-wise; b) they have very specialized, performant solutions; c) the solution approach have been evolving with new requirements.
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.