I’ve got a pretty good idea what OO is personally, and I have some pretty good reasons for not liking it. You might want, for instance, to have a read over Data Oriented Design. That goes into some of the reasons why OOP as it is currently practiced is a lousy match for modern hardware, particularly when it comes to CPU caches.
I’d also argue that inheritance in general has turned out to be a bad idea in most cases. Not in all cases; you can occasionally point to a place where inheritance is a reasonable fit. Most of the time, however, inheritance is the wrong tool; you can use composition to do everything inheritance could do (and more) without the forced relationships that come from inheritance.
Composition is not inheritance, and doesn’t need to be built on it. You can potentially use both; an object could inherit composed components. But they are different things. Composition is embedding discrete components in a larger object or structure, while inheritance is essentially a templating system with overrides. The dependency graph of a composition system tends to be much sparser than the dependency graph of an inherited system, because the composition system is explicit and direct (you have to specify what you want to include, from where) while the inheritance system is implicit and cascading (you get everything unless you specify otherwise, and depend on everything above you). That implicit nature of inheritance means every time you make a new kind of object, you’re adding new edges to the dependency graph.
Those dependency relationships come with design ossification and maintenance costs. They make structural refactoring significantly harder, which means in a real project (particularly one that’s already shipped a version and needs maintaining) there tends to be increasing pressure to hack around problems rather than address them directly if they would affect the layout of the object hierarchy. Yes, the right thing to do is to fix the structural problem, but that’s going to take way more time and effort than hacking, and time may be of the essence if your customers are waiting for a fix.
I would also argue that encapsulation has major tradeoffs involved with it. The main reason for the recent proliferation of signals and message busses in OOP systems is to end-run around encapsulation, which means we’ve reinvented GOTO and added ad-hoc multiple-target unordered cross-module flow control to it. Really, we’ve reinvented Intercal’s COME_FROM, which is even worse. This does not feel like a win to me, and I have plenty of empirical evidence to back me up on that.
You have not seen true spaghetti code until you’ve seen a threaded OOP system that makes heavy use of async messaging. Usually the first clue you’re in for a ride is finding random sleep calls in signal handling code where someone’s tried to hack around a signal delivery ordering problem.
As for “data with methods”, see the data oriented design book above.
I have been on more than a few large projects at this point. I have worked on OOP commercial products built in C++, ObjectiveC, Swift, Java, Kotlin, and a variety of other languages. Everything I have seen so far has led me to believe that OOP was a massively overhyped fad that has set computer science back decades. It was already clearly not a great idea in the 1990s, and it has not aged well. It’s telling that a lot of the newest crop of languages (Zig, Odin, Jai…) are leaning away from OOP or making it optional.
My experience has been that OOP as a design philosophy is a great way of getting too far out over your skis. It makes it extremely easy to build complex systems, but gives you terrible tools for maintaining or understanding complex systems. It is very easy to get to a place where nobody knows how the whole system works any more, which means it’s very easy to get to a place where bugs arise from internal interactions that nobody understands.
This is entirely leaving aside problems with specific OOP implementations you can actually use, which are a rich field in which many rants could be grown.