I don't believe in Object Oriented Programming anymore

As the title implies, I don't think I believe in Object Oriented Programming anymore. It's been this way for a few years now, and with every passing year I become more certain of my sentiment. The idea presents a promise it is unable to uphold, making it all the more surprising why it holds such a chokehold on the current industry. I therefore encourage developers, especially junior or aspiring developers, to be wary of investing too much time in learning all the nuance behind OOP. Do so if you wish - but please, become comfortable writing code and solving problems first. Then try out OOP to see if it sticks with you.

I've been somewhat back and forth on this during my career. On the one hand, the conceptual idea of OOP feels good. "Here's my thing, which solves my task." - have enough things, and you can solve every task. It's a sort of way to look at a problem, break it up into parts, solve each part and then solve the problem. Sometimes your things end up being called ThingDoer or TaskSolver, which is a bit funny to read, but that's besides the point.

The thing is, I've probably always thought about things that way. Break the problem into parts, solve the individual parts, then solve the problems. Only difference is that prior to learning OOP, the individual parts were just functions who were handed data to be passed between them. There was less ceremony, sure, but things were more uniform, so things felt easier to combine and adapt to one's ever so slightly changing needs. Everything was either data or operations on data. Methods end up being a weird, sometimes confusing middle-ground. When static, they are just like functions, when written as a getter, they are (almost) just like data. When written in any other fashion, the line is harder to draw.

var x = doThing(a, b);

Take a small look at the example above. It seems simple, right? It probably is, but I've written it in such an abstract fashion, that it's hard to say (sorry). Let's reason about it for a bit. What does it do?

Is there anything else it might do?

(This is one of the reasons why some people don't like global variables)

Let's look at the more OOP counterpart:

var x = thingDoer.doThing(a, b);

What can we say about this method? Well:

Is there anything else we can say?

Just like with globals, we can't determine what it will do to its own members. Unlike globals, we most likely can't even observe the members. Is this bad? Not necessarily. In the singular case, this is manageable. But the more of these cases that exist, the more data manipulation and complexity you have behind the scenes. In most OOP-codebases, methods are everywhere, meaning data risks being manipulated willy-nilly without a feasible way of tracing it.

Death by a thousand abstractions

The word "abstraction" sometimes seems like the most loaded word in software development. Some people love it, some people hate it, most probably fall somewhere inbetween. Conceptually, I find it, at least on the surface level, to be a very good idea in an almost innocent fashion. I'm very afraid of the idea of people taking it too far, however, and I find OO-code to often cross this line.

Let's look at another example.

class Person {
    String getName();
}

This, once again, is a fairly forgiving example. It contains a single class with a single accessor method. What's "wrong" here? Well, in the singular case, not that much. It's when it becomes a repeated offense that it's hard to handle. The idea behind doing things this way is that if ever want to change the way in which the name is extracted out of the person, we just alter the method and the change is applied throughout the codebase. Most cases don't require this, but the escape latch is supposedly nice. In a sense, we've made an abstraction over the field, something we can call to get the field.

But why?

Best case scenario - we actually end up using it. Somewhere, we need to introduce some computation, be it a map lookup, a socket read or what have you. In this case, all of your getters are a basic function call, except one. They all look almost the same, so give it a few weeks and chances are good you may have already forgotten about which field had an accessor which did something more than just accessing the variable.

Worst case scenario - we don't need it. We now have thousands of almost empty function calls littered across the codebase, the total code used to solve the problem has increased, and if you keep going at it, you'll eventually begin to see performance issues (just start your favorite Spring application and you'll get to experience it firsthand).

What's the alternative?

struct Person {
    String name;
}

String readNameFromSocket(Person, Socket);

I think this is the way. Keep data as data. When (if) you need to "get" or "access" the data from somewhere, you make a function which does that. You don't have to second-guess whether accessors do or don't have side effects. The function which deals with accessing the data can describe how it intends on getting the data (e.g. via a socket) instead of being forced into whichever accessor-prefix you prefer.

Sure, you've lost the powerful abstraction of being able to change the behavior of all member access at the same time. But I don't think it's ever about having powerful abstractions. I think it's about having the right abstractions. That stuff is subjective. But I do generally find that the further down the tree of abstractions you start, the easier it is to create an abstraction which feels right for your use-case. And an easy way of doing that is to get rid of the overwhelming amount of classes and members that object-oriented programming entails.

Thank you for reading.