May 16, 2020
The problem with OOP isn’t the “object” part, it’s really the “oriented” part.
My problem with OOP isn’t really that I despise design patterns, nor that I like all of my data to be in global scope, which OOP at least discourages, nor that I’m convinced that all functions should be pure and any state is the root of all evil. My problem is simply that it doesn’t really solve the problems I have when I’m programming. It does little to solve the problems I do have, and it imposes limitations and shepherds me into getting problems that I didn’t have before. Even calling this a trade-off is generous, since a trade would imply that I actually am getting something out of it.
It is very rare that I know up front exactly what I’m making and how it will look in the end. Most of the time I have some high level idea of what should happen in my program, and I need to figure out how to integrate this in the current codebase. This means understanding what data I need to transform and how to organize this data. Sometimes this is straight forward, and sometimes this reveals that my high-level idea of the problem was wrong, which means I have to go back and adjust my high level idea, this time with more information in mind. This process goes back and forth, and in the end my program might look nothing like my first attempt.
I think most programmers experience this often.
Since I have to go back and forth a lot when developing, most of my code will not make it far. This is a crucial counterexample to the meme that “code is written once but read a thousand times”. Most of the code I write is probably just read a couple of times (and only by me!), before it’s replaced by new code that better solves the problems I’m having. Therefore, spending time on this code is very often a complete waste of time.
In OOP it’s central to make class hierarchies with methods that the subclasses override, and having the callers of these methods be agnostic to the actual class of the objects they are operating on. Having had some experience in languages in which this is either discouraged or simply not possible, I’ve come to the conclusion that having a superclass that defines methods and a number of different subclasses with which the methods are automatically dispatched, is really not something I need that often. Maybe I can come up with an example where this is actually a good solution, given some time I probably can, but this comes back to the second O of OOP: “Oriented”.
The case where I actually want dynamic dispatch is very rare, and so having the entire programming language (or worse, your codebase) be oriented around this concept does not make any sense. Similarly, most of my types are significantly different enough for it not to make sense having code agnostic to the types it’s operating on. It just doesn’t really happen that often.
Similarly, having intentionally been very generous with visibility qualifiers in my code, I cannot think of a single time where a member being visible has caused any problems. On the other hand, I remember vividly a case where a 3rd party library had messed up the qualifiers on a tuple-like type containing the coordinates of a mouse click, which made it impossible to get them. Try to think back: when was the last time you tried to access a field or method, only to get told that it’s private, and then being forced into a setter/getter with other logic which saved you from a bug?
OOP is about structuring your program according to certain principles. That is, in of itself it doesn’t to any work. Thus, the time you spend on making your codebase adhere to OO principles is time spent not making your program do what it needs to do, and thus wasted. That is, unless it pays back later.
I think what often brings people into OOP is the idea that the work you spend on having a good class structure, with subclassing and getters/setters and proper encapsulation, will pay back in dividends during the lifetime of the project by avoiding code duplication, maintaining invariants on a class’ private state, help debugging, making your code more flexible, and a bunch of other things.
So by spending time designing class structures, figuring out which fields are properly encapsulated, and making sure methods are abstracted sufficiently high enough up in the hierarchy, you’re spending time in the hopes that this will make it easier to work with down the line.
You are, effectively, paying the price to fight a problem you think you might get further down the line, and you hope that the price, which you definitely paid, was sufficient for these problems to not get out of hand.
I’m also becoming increasingly vary to complete encapsulation.
At it’s essence, encapsulation is about only exposing the minimal subset of the members (data or methods) of a class that the caller needs.
This sounds rather reasonable and sometimes it is a good idea.
However, it also opens up an extremely difficult problem for the programmer,
because they now have to decide exactly which subset of their members are sufficient for any possible use-case of that class.
When marking a field
private, the programmer is really saying that there is no possible valid program which
needs to access this field, whatsoever.
I genuinely think hidden by default is the wrong choice. You shouldn’t have to make the case to have a function or data members visible, because now the programmer will have to imagine a valid use-case in which having this member visible is necessary, and there will always be use-cases that they miss.
The mere presence of dynamic dispatched method calls doesn’t make a whole codebase OO. Batching together data into something that looks like a class certainly does not make the codebase OO. Having subclasses does not make the codebase OO. For a codebase to be Object Oriented, it really has to be oriented around objects.
I don’t this the OO mindset is without merits, but I think the gains are sufficiently far and few in between that having your codebase be oriented around objects is a mistake. Rather, I think the codebase should be oriented around the data that your programming is operating on; after all, everything a program does is transforming input data to output data, and the codebase should reflect this.
At last, don’t start quoting Alan Kay; I know his objections to what we now call OOP, and if you’ve made it this far I’m sure you understand that I’m not talking about his idea of it.
Thanks for reading.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License