The compiler not catching it is a limitation of the language he uses, not the limitation of the general concept of switch / pattern matching. Scala, Haskell, Rust do catch those.
> If you follow the principle "switch statements over [X]", try to add a new shape down the line and see how quickly you run into problems.
And who said you'd ever need to add a new shape? Maybe you will need to add a new operation? Try to add a new operation `calculateWhatever` and see how many places of the code you need to chnage instead of just adding one new function with a switch.
Often you really don't know in which direction the code will evolve. Most often you can't guess the coming change, so don't make the code more complex now in order to make it simpler in the future (which may never come).
You're thinking of defaultless switch statements. Rust and Typescript catch those issues as long as you don't add a default. I'd already thought of it when I typed it
>And who said you'd ever need to add a new shape?
ô_o
>Maybe you will need to add a new operation? Try to add a new operation `calculateWhatever` and see how many places of the code you need to chnage instead of just adding one new function with a switch.
You do realize that your "one function with a switch" does have to cover every case exactly like the clean code version, it's not magic you're just fitting it all into one switch/case, and you'll probably end up extracting those case: blocks into separate functions anyway.
And on top of that you're not safe from a colleague adding a useless "default" case at the end ouf ot paranoia and making your compiler not catch a future problem when a new shape gets added.
Productivity is achieved in spite of clean, not thanks to it.
The difference is, adding "default" cases to complete switch statements happens all the time, you've probably witnessed it, I know I have, whereas colleagues adding a dozen classes to add two numbers doesn't.
You're also never safe from meteorites crashing your building, let's talk about real antipatterns
But the same problem exists with interfaces and virtual dispatch! If you provide a default implementation at the interface / abstract class level, then the compiler won't tell you forgot to implement that method, because it would see the default one exists.
And one should be able to fix things that get out of hand when they start getting out of hand. Architecture should be based on current facts, not on our fantasies about the future.
For some reason we're always making this weird assumption that future engineers working on a problem are going to be less capable than us. So we decouple things in advance for them. Those future idiots won't know how to architect for scale, but we, today, with our limited domain expertise, know better.
The reality is that in most cases we are those future engineers. And, unsurprisingly, "future we" tend to know more, not less.
In the clean code version, your compiler will remind you to implement calculateArea, calculateNumberOfVertices, calculateWhatever, and so on and so forth.
With his version you have to add a new `case` to every switch statement and hope you didn't miss one with a default case, because the compiler won't catch it.
You don't need virtual functions and polymorphism to remove his switch statements. Just compose over inheritance.