To give another side to this argument, object oriented design isn’t wrong in itself, but it makes bad design seem easy and natural at first. You see these examples everywhere in “the wild” because they’re the easiest ways to use an OO language. You can go very far on a project before running into one of the downsides of these, but unfortunately it can be too late when you do. It’s gradual but it feels very sudden due to the interplay between OOP and agile software development when viewed as a system.
My experience has been like this many times; your team develops an architecture and labels features as easy or hard comparatively and low hanging fruit get scheduled and done. This reinforces, rather than challenges any mistakes made in the app’s design. Meanwhile, velocity is high and the burndown chart seems to be moving. You get to a point where a big feature that works contrary to the current architecture is business critical and can’t be avoided. It’s much harder than it needs to be because it cuts across the grain and impacts lots of loosely coupled systems, and the difficulty seems to come from nowhere for someone outside the project’s programmers. You work on it, it’s buggy, it makes other things buggy, poor type systems cause unintended side effects (especially in C++).
It’s not strictly an OO problem. One can commit many of the same sins in a strongly typed FP language when writing some code. The problem is that OO can’t really help to get out of them easily on its own. TDD is close to the cushion required, but unlike a good type system, it doesn’t change automatically with your code and therefore has a lot of resource allocation pressure on it.
FP forces you to either throw away intermediate state or make it visible. Invisible state can’t make your app work wrong, and the only thing that could possibly be breaking your app if a change you didn’t make suddenly breaks something is one you can see as a function result. Even coupling via object references is hidden state, because it allows unseen forces to modify objects. This is why people say “encapsulation is broken by object references”; you’re exposing an object to potentially many collaborators that will all mutate the same object in different ways. IMO, you can have an OO language that is also functional (write const methods returning objects instead of mutating the current object), but it’s not natural in most OO languages since they concentrate on making mutation convenient.
Secondly, a strongly typed FP language generally forces you to use correct argument types, not just compatible or convertible ones, and makes introducing new types natural. Compare to OO languages where unsafe casts, nulls and “type assertions” might be common. This also protects refactoring against subtle problems that creep up and can be hard to fix even if you get a sensible enough stack trace to find them.