> having all procedures that operate on and change the state of an object in the same file is super helpful.
How many OOP programs do you know that have achieved this? None that I know. Perhaps utility libraries can get close, but no software actually solving domain-specific problems. Objects don't live on their own, in a typical OOP program there are bits of code all over the place that operate on your data objects. Finding all the ways that a personel record is modified can be a tricky problem. More so because the object is likely to be a bastardized combination of personel data and software engineering level-of-abstraction (see below).
> Being able to make guarantees that certain state cannot be changes outside of the organization unit of the class(encapsulation) is super helpful
Agreed, but woefully limited. The idea that a class should be the unit of encapsulation leads to all kinds of horrible programming idioms, leaked abstractions and interface complexities. A better solution would be to erect encapsulation barriers explicitly, rather than tying them to an unrelated domain-modelling semantic.
> Being able to abstract over certain related but non identical functions without having to resort to switch statements is super helpful.(polymorphism)
Or just polymorphic dispatch, which if you're going that way, you can add multiple dispatch and get much more power, security and expressivity.
> And the ability to reuse code when different pieces of data share some identical features and we want to abstract over a portion of those features is useful(inheritance).
Except that again, OOP encourages programmers to think of code reuse in terms of an unrelated domain-concept, which leads to class design that is motivated by functional needs, rather than domain needs. Which is again a bane of real OOP systems.
> OOP doesn't give you any abilities you didn't have before, and you can write great readable structured procedural code.
Agreed. You can write OO procedural code. I've written quite a lot of OO C, for example.
> But it does give you more tools in your tool box that when used correctly can help structure your code in a way that limits the burden of the software's complexity.
I'm skeptical that 'when used correctly' is anything but a no-true-scotsman fallacy. In practice, the way OOP pushes together concerns of domain, semantics and syntax seems to encourage code that lacks the kind of encapsulation, reuse and separability that OOP-devotees claim is the raison d'etre of their paradigm.
I bought OOP in a big way through the 90s and into the early 2000s. I'm sorry to say I think the emperor has very few clothes. The realities of the big software systems you cite seem to be very different from the clean simple benefits demonstrated on toy problems in CS classes.
Three issues I've hit time and time again, in the millions of lines of OOP I've written and maintained: one is the expression problem, a second is the fragile base class, and a third is the metaclass problem (where the domain requires three levels of modelling, but the language provides two, or provides metaclasses with semantics that 'break' the principles of OOP you mention).