Notes on why OOP is bad (and how to solve it)

These are my study notes on a controversial video published on YouTube in 2016 by Brian Will called "Object-Oriented Programming is Bad".

The video addresses many of the concerns I've had ever since I first started learning Java and OOP years ago. Since then I have switched to C#, but the concerns have remained mostly unchanged.

The solution Brian Will suggests on the video is a combination of procedural and functional programming.

From where I'm standing his solution seems ideal to many of my problems. Separation of data from logic clicks hard with me and I absolutely love certain concepts in functional programming (such a purity of functions) while I would not necessarily want to program fully functionally or abandon the notion of classes and objects altogether.

If you are interested in functional programming in C# in particular I recommend Enrico Buonanno's book "Functional Programming in C#", which I own both as a paperback edition and as a digital book.

However functional programming doesn't come without it's set of problems (to which I won't go in a depth here) and the combination of procedural and functional Brian Will suggest in the video below is a perfect match in my opinion.

My study notes on the video start here:

Problems with OOP:

NOT the problem:
- Classes
- Performance
- Abstraction
- Aesthetics

Code Aesthetics DO matter:
- Elegance
- Simplicity
- Flexibility
- Readability
- Maintenance
- Structure

Competing paradigms:

Procedural and imperative (the default)
- Procedural: "No explicit association with data types and functions and behaviors."
- Imperative: "Mutate state whenever you feel like it. No special handling for shared state. Cope with the problems as they arise."

Procedural and functional (minimize state)
- Get rid of state as much as possible.
- All or most of the functions should be pure, they should not deal with state.

Object oriented and imperative (segregate state)
- Segregate state. Divide and conquer the problem. Separate everything into objects.
- Segregation is a viable strategy up to a point of complexity.

Claims:
- Procedural code is better (even when not functional)
- Inheritance is irrelevant.
- Polymorphism is not exclusively OOP.
- Encapsulation does not work at fine-grained levels of code. This is the biggest problem with OOP.
- Fractioning code artificially to follow OOP rules leads to unnecessary complexity which is hard to follow.

What is an object?

An object is a bundle of encapsulated state. We don't interact with the state of that object directly. All interactions with that state from the outside world come in through from messages. The object has a defined set of messages called it's interface. We have private information hidden behind the public interface. When an object receives a message it may in turn send messages to other object. We may conceive of an object-oriented program being as a graph of object all communicating with each other by sending messages.

For object A to send a message to object B, A must hold a private reference to B.
Messages may indirectly read and modify state.
The moment objects are shared encapsulation flies out of the window.

Solution, procedural programming:

- Prefer functions over methods. Write methods only when the exclusive association with the data type is not in doubt, e.g. ADT's (abstract data types like queues, lists, etc.)

- When in doubt -> parameterize. Do not encapsulate the state of the program at fine-grained level. However, shared state is a problem. It cannot be fully solved without pure functional programming. This means that rather then passing data through global variables you should instead make that data explicit parameter of the function so it has to be explicitly passed in. Any time you get the temptation to pass in data through a global because it's convenient you should seriously reconsider.

- When you DO end up with globals - Bundle the globals into structs/records/classes, ie. bundle them logically into data types. Even if this effectively means you have a data type with one instance in your whole program this little trick can often make your code seem just a little bit more organized. In a sense you are just using data types in this way to create tiny little sub namespaces. But if you do a good job logically grouping your globals this way as a side benefit this supports parameterization - you can more conveniently pass this global state to functions.

- Favor pure functions. If you see an opportunity to make a function pure it's a good policy to take that opportunity. Pure functions are easier to understand and to make correct. This is easier when efficiency is not a priority.

- You should try to encapsulate (loosely) at the level of namespaces/packages/modules. Think of each package as having it's own private state and public interface.

- Don't be afraid of long functions! Most programs have key sections. The problem of extracting everything into separate functions breaks up the natural sequence of code and is spread out of order throughout the code base. Prefer commenting each section of the function when needed over extracting the sections. Keep in mind general code readability for long functions:  Prefer extracting to nested functions. Don't stray from left margin too far or for too long. Watch out for too much complexity and split up and extract to other functions as needed. Constrain scope of local variables. When in doubt enclose variables into curly braces to limit their scope - even better, enclose them into their own anonymous functions.

Good luck!