> And before anyone goes and comments aksully code organization blabla

I'll bite. Having worked on "enterprise" application the OOP (or more like the dreaded enterprise patterns) have sort of "clicked" to me.

An aside. Git-flow is more than decade old at this point and people still have heated debates from time to time on whether that approach is any good. In essence git-flow merely introduces organization to patchsets. If you are building a SaaS like system, most probably every environment you have runs "current" code and each stage (prod, test, dev) is merely a shifted pointer that will eventually catch up. Why would you structure your teamwork around identifiable patchsets, why would you keep that information around when you do trunk-based development? There is seemingly no need to do that and you would rightfully diss on git-flow as overly complex.

However, if you build hardware appliances or installable offline application with multiple versions (think e.g. MS Office in 2000-2010 - multiple versions and tiers of those out in the wild under support), suddenly there are situations where you do in fact need to checkout and work on an exact patchset that is installed at customer site. If your field requires any form of certification, you will quickly learn that having patchsets provably not touching frozen (already certified or submitted for certification) code is highly valuable. Ability to cherry-pick a hotfix can save organization man-months.

Back to code organization. So you have this "enterprise" application that has various software modules/components, some of which are built by external contractors, some are off-the-shelf components, some have been left and forgotten five years ago and for one reason or another you want to work on one of these components. Whatever team you assemble, majority will have zero understanding of assumptions baked into code. Documentation, however meticulously maintained, will still leave holes in understanding. How do you make changes to a decade old component that is used in various weird ways all throughout the project with any amount of confidence that you are not breaking stuff at a distance? Apparently, OOP enterprise patterns that introduce decoupling points for implementation details and pass around objects with assumptions abstracted away are pretty solid barriers. They are cumbersome, difficult to use at early stages of development when all complexity of the task can fit into the heads of programmers implementing features. Similarly to git-flow, the advantages of these structures only become apparent when you start needing them and that will quite probably be when the principal engineers greenfielding the project have long left the company altogether.

A very specific example: factory pattern. Why would you have a layer of indirection to simply instantiate an object? All refcounting shenanigans aside, you just cannot change interface of object/component without affecting call sites. Call sites that may be outside of your control, code frozen or simply too dreadful to touch without strong justification. Factory pattern introduces a decoupling point between call sites and implementation. Having a factory you can implement interface shims and leave call sites untouched while having the freedom to rework component and its interface. You just cannot achieve that without OOP.

> Let alone that the entrenched, canonical, idiomatic coding styles of most modern oop languages encourage state, mutability, nulls, exceptions and god knows how many trivially preventable entire classes of errors.

I will agree that OOP implementations in mainstream languages leave much to be desired. That is part of the price we pay for backwards compatibility.