Object-Oriented Programming Considered Harmful

Object-Oriented Programming is considered to be the gold standard. Until it is no longer working.

Photo by OSPAN ALI on Unsplash

The False Object-Oriented Programming

I’m going to start this article with a bold claim. What is widely known today as OOP is nothing but a terrible mistake.

Now that I have your attention, let’s turn to the supreme authority on the topic of object-orientation, Alan Kay, the genius who has invented OOP:

I invented the term Object-Oriented, and I can tell you I did not have C++ in mind.

- Alan Kay, the inventor of Object-Oriented programming.

It all started with C++

Yes, the modern OOP started with C++. Yet C++ was never a well-designed language. Mainly because it was designed in 1979, and at that time language designers lacked the experience, and didn’t know what to focus on. C++ attempted to cram in as many features as possible. OOP simply happened to be a “cool new thing” back then.

C++ has implemented the very superficial features of OOP such as inheritance and encapsulation, while completely missing Alan Kay’s key ideas. What exactly has C++ (and consequently other modern OOP languages) missed?

Here’s another quote from Alan Kay:

I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is messaging.
- Alan Kay, the inventor of OOP

What the designers of C++ have completely missed was the very core idea of OOP — messaging. Yes, C++ has methods. But methods are not messages.

It was all about cells

Alan Kay had a background in biology, and had an idea of designing programs in a way that resembles biological cells. The biological cells are completely independent, and can’t access each other’s internal state. The cells can only communicate via messages (e.g. neurotransmitters).

And Alan Kay has successfully implemented the “proper OOP” in Simula and later in Smalltalk.

What about C++, Java, C#, TypeScript, and other modern OOP implementations? Method calls often receive references to other objects as arguments, that are then being stored in the internal state of the object.

In the above example, garfield object is being stored somewhere inside the catManager object. This means that garfield and catManager do not communicate via messages. What this means is that the catManager has access to the entire internals of garfield , and other cats it manages… This in no way resembles biological cells.

Blurry boundaries

One of the best practices in software is to have clearly defined boundaries between modules.

Yet in the above example, the line between garfield and catManager becomes erased, effectively merging the state of two.

In languages like C++, Java, C#, and even TypeScript this lack of proper messaging has huge implications — the entire program effectively becomes one blob of shared global state. The state of the entire graph of objects becomes merged into one huge blob of global state.

What we have today is far from Alan Kay OOP, where the objects are independent cells communicating through messages.

Since objects internally store references to other objects, we have something more of a cell inside of a cell inside of a cell inside of a cell. Making one huge mess of a cell.

It is universally accepted that global state is to be avoided. Yet this is exactly what the modern OOP is. Just like global state, OOP is better avoided.

Multicore?

OOP was created in the days of single-core computing. It goes without saying that OOP is ill-fitted for the modern concurrent multi-core software.

The promiscuous sharing of mutable state in OOP code makes parallelizing such code almost impossible. Complex mechanisms have been invented in order to address this problem. Thread locking, mutex, and many other mechanisms have been invented. Of course, such complex approaches have their own drawbacks — deadlocks, lack of composability, debugging multi-threaded code is very hard and time-consuming. I’m not even talking about the increased complexity caused by making use of such concurrency mechanisms.

Java and friends

If C++ wasn’t bad enough, Java soon followed suit. Yes, Java made some great improvements over C++ (garbage collected, cross-platform). However, fundamentally, Java suffers from the same fundamental OOP design flaws present in C++ — lack of proper messaging.

Once again, to quote Alan Kay:

Java is the most distressing thing to happen to computing since MS-DOS.

- Alan Kay, the inventor of object-oriented programming.

After Java further popularized the flawed OOP implementation, C# soon followed suit.

In the following decade the popularity of languages based on flawed OOP implementation exploded: Ruby, TypeScript. Even JavaScript and Python have added support for OOP, just because they could (and not because it ever was a good idea).

What do we have today? A whole bunch of modern programming languages, that are based on a flawed OOP implementation. And such popularity indeed has caused an innumerable number of bugs, causing terrible damage to companies using OOP (and their customers), ultimately culminating in a trillion dollar disaster.

The Problem With Design Patterns

Finally, let’s talk about the revered OOP design patterns.

Have you ever wondered why does it take so much effort to become good at OOP? Why does it take so much effort to build something that is somewhat maintainable? One can simply ignore the complexity of OOP, and argue that all software is extremely complex. But this would completely miss the point.

It takes years (if not decades) to become good at OOP. Being good at OOP requires knowledge of myriad of design patterns. I’d argue that it is impossible to become good at OOP without years and years of mentorship.

Why does it take so much knowledge and experience to become somewhat productive in OOP? It really comes down to design patterns.

On the surface, the OOP design patterns may seem like a great idea. Something that every self-respecting software developer should have mastered. A “proper” way to organize software, to address the shortcomings of the modern OOP itself.

Yet the design patterns are nothing more than a band aid for the shortcomings of OOP. People have invested a lot of time and effort into OOP, and simply couldn’t throw it away, despite its shortcoming. Design patterns is an attempt to save something that is inherently flawed. Imagine a project that lacks a solid foundation. The project keeps accumulating hacks, and then more hacks, and even more hacks on top of that. However, this time people say “How about we give the hacks that we came up with a nice name? Maybe design patterns?”.

Why are things that way?

Sunk cost fallacy

Let’s say you’ve purchased a concert ticket a month ago for $50. On the day of the concert there’s a snowstorm, and you feel sick. You know that the traffic will be really bad because of the snow, and you will get even sicker because of going. The drawbacks outweigh the benefits of going, yet you still decide to go to the concert.

What is the sunk cost fallacy?

If we’re invested in something, then we’re likely to continue the endeavor. Even if the evidence shows that it is no longer the best decision.

The situation with OOP is a great example of sunk cost fallacy in action. People keep investing into something that is broken.

The design patterns add a lot of unnecessary complexity. They make the code less readable, less reliable, less scalable, and less maintainable.

There’s a solution

Photo by frank mckenna on Unsplash

No article criticizing OOP goes without mentioning a solid alternative, that actually solves most of the problems created by OOP.

The solution is Functional Programming. It solves most, if not all problems inherent in OOP.

And what are some of the problems inherent in OOP? Complexity caused by shared mutable state and the complex design patterns.

Functional Programming doesn’t have mutable state, it has immutable state.

Functional Programming doesn’t have the myriad of design patterns (if you stay away from Haskell). If anything, Functional Programming is the synonym of simplicity (once you get past beginner stage).

Functional Programming encourages the use of pure functions, which means very well defined boundaries. Whenever you’re working with a pure function, the function itself is the boundary. And you don’t have to worry about anything else apart from the function you’re working with.

Erlang/Elixir are modern languages that were inspired by Alan Kay’s OOP — they have completely independent processes (i.e. biological cells) that communicate with immutable messages. And it doesn’t use the myriad of design patterns. Same goes for SmallTalk. It doesn’t need the design patterns, because the OOP implementation is not flawed. Other programming paradigms such as Procedural and Functional Programming don’t need such complex design patterns, because the programming paradigm is not fundamentally flawed. There’s simply no need for such band aids.

Design Patterns in Functional Programming?

Factory pattern? Not a pattern in Functional Programming. Just use a plain old function returning some data.

AbstractFactory? Doesn’t exist in Functional Programming.

Command or Strategy pattern? No need to call it a “pattern”, simply pass a function.

Dependency Injection? Simply partially apply a function, by calling it with one parameter. Not even a “pattern”.

What about the dreaded “monad”? You don’t even need to know what it is to be productive in most functional languages (Elm, ReasonML, OCaml, Elixir, F#).

There’s one pattern in FP that has to be learned, and that is programming with immutable state. And even then this is not a “design pattern”. This is a fundamental property of FP, that is built in into all functional languages. No need to worry about the “design patterns”. A functional language enforces everything for you.

Pits of success

There’s great talk by Mark Seemann, who used to be a very prominent figure in the OOP circles (he’s written the seminal book Dependency Injection in .NET). In that talk he argues, that OOP is similar to trying to balance a heavy ball on the top of a hill. It takes a lot of effort to roll the ball upward, and then takes a lot of effort to keep it there. Due to different forces, the ball is naturally inclined to roll back downhill. Perhaps this is even one of the reasons for the famous programming adage “if it works, don’t touch it”.

Functional Programming, on the other hand, is similar to making the ball roll into a pit. We make use of the gravity to help us roll the ball into a pit, and it will stay there effortlessly, thanks to gravity. With OOP, we fight against the gravity every single second. With FP, the gravity becomes our friend, we no longer have to fight against it.

Undoing the damage

Photo by Raph Howald on Unsplash

As many of you might have noticed, writing about Object-Oriented vs Functional Programming is my favorite topic.

Why? Because OOP has done trillions of dollars of damage in the past few decades. We have fallen into the sunken cost fallacy, investing more and more resources into something that is inherently flawed and can’t be saved.

However, the bright minds of the world have come up with really solid alternatives. It’s time for the entire software industry to get more educated, learn the alternatives, and start applying them as soon as possible.

One of the main qualities of good software is simplicity. Simply code is reliable and maintainable code. And the design patterns add unnecessary complexity.

The next time you reach out for OOP design patterns, ask yourself — do you really need them? Or maybe the time has come for you to leave the sinking ship?

And of course, do your part, share this article. And then learn Functional Programming, start applying it, and teach others.

Thanks for reading!

Senior full-stack engineer. Elixir/ReasonML/React.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store