Oh boy. Here we go, here I am commenting on a subject on which _everybody_ has an opinion. A lot of people say that "OOP" is just a tool. Or that it is overused. Or that it isn't used properly. I will say that even though I sometimes use objects I am not a fan of almost every "object-oriented" codebase that I've had to work with. The issue is that OOP is presented as an abstraction that, if you understood it, would provide for good readable code. The way I judge good principle is whether when, inevitably, junior to mid-level programmers start working on the codebase will they be able to preserve some amount of sounds structure. I would say that OOP offers very little here, as I've seen even senior developers write what to them was "good OOP code" but in practice nobody could work with.

Here are some things that OOP does not give you:

* Is your state normalized, i.e. saved only in one place? You do not know.

* Is your execution predictable? Parent to child? And how do you do it when it's reversed.

* What should be public and modifiable and what should be intermediate state internal to the object. And should intermediate computed values be stored next to values determining the true state of your application? (even though there are "public" and "private" modifiers it is quiet difficult to determine what is truly public and cannot be modified through some method. This makes it more difficult to understand what the state of your application truly is and what are intermediate convenience values).

* Are there limitations on passing state around? Or can large chunks of state be passed to multiple actors in the form of classes who change it concurrently or might store it for later use?

* How much state should be in a single class? In other words: how many classes can a class encapsulate and how complex could those classes be?

* Should something that has no state be an object or just a function (in some languages you are not given the option at true, but can't say that it's awesome).

* Should you be allowed to use event buses and how to prevent spaghettification? (cause events really are akin modern goto statements).

* etc.

Basically the ability to compose the state, type and methods is so wildly open-ended that almost always what I see is just an ever expanding convoluted bloat of state spread ts chaotically that it become extremely difficult to see what the state of your application is at any moment. Everybody has their own idea of what an object _is_, what should be an object and how they should come together. And that is for every developer on the team. So you can follow the absolute best of OOP practices and still end up with a mess.

Let's look at a counter-example. It is not a system that claims to be universally usable but that is the point. I'll use the, quiet banal, example of React. Yes, it is a framework, and in a way it does have objects, but really it borrows heavily from functional programming in the sense that it says that state is king and the single most important thing in your program. That you _must_ have strong constraints as to how your state is created, organized and modified. (In contrast to a class, which is state that you can pass around as much as you want and the whole codebase could modify it at will).

* The application is always a predictable tree of dependencies.

* State is either stored separately altogether (using a state-store such as redux, mobx, etc.) or bound to a particular element in the tree hierarchy.

* State only flows down the tree, ideally immutably.

* A component (unlike a class) cannot modify state of anything outside of itself.

* Only "has-a" relationships are allowed - no polymorphism.

* Only event can be passed up.

* State is essentially a collection of easily inspectable primitives. Meaning that even though a component can _contain_ another component a class is not a valid member of component state.

While a system like this could be built using OOP primitives (classes). Nothing about OOP suggests this system. Yes, you can build it, but can build something absolutely unrelated. And, really, it enforces things that are the antithesis of many things that "proper OOP" espouses.

Perhaps the main thing of any system is to create a predictable and well-structured relationship between elements of state. I think that wonderful examples of the basis of such a system are Go and Rust. To be honest, I barely know the languages, but from what I see is that they make _type_ the most central element of the language. If you have your types well-defined and be separate from how the types are used you are already suggesting many things that could prevent state-sprawl. A struct defines the basic primitive around which your program is based.

* Struct has no private intermediately computed values. If you do not want to recompute something - memoize, do not bloat the state with random data that is inseparable from you base state.

* Structs provide a very predictable "has-a" top-down dependency between state

* No inheritance, so composition is enforced.

* Keeping types and functions that operate on those types makes it very explicit what a function does - is it a method or is it a function?