Is Object-Oriented Programming an Overrated Garbage?
An analysis of common arguments against OOP
Cover Photo by Jilbert Ebrahimi on Unsplash
In the one and only true way. The object-oriented version of 'Spaghetti code' is, of course, 'Lasagna code'. (Too many layers). ROBERTO WALTMAN
Object-oriented programming (OOP) as an idea has been oversold. The majority of today's languages are built on the concept of OOP. Extremist programming languages, such as Java, require you to conceive about everything in terms of objects.
But is object-orientation (OO) a good idea? Does it have problems?
Inheritance, encapsulation, and polymorphism are the three main pillars of OOP, with abstraction added as the fourth.
I was surprised to know that these pillars have major issues. This post will be limited to the issues of the inheritance pillar. I will analyze whether these are actually issues concerning OOP.
Inheritance
Inheritance looks like one of the biggest advantages of OOP. All simple examples of inheritance make sure that they make perfect sense. The terms reuse or reusability are used quite often when discussing the pillar of inheritance.
Source: Charles Scalfani
Banana Monkey Jungle Problem
This is a classic example in the list of OOP haters. This problem describes situations where, for you to reuse an object, you need the whole list of its parents. You'll need the object's parent and its parent's parent, and so on and so forth, for each contained object and all of the parents of what those contain, as well as their parent's parent's parents...
There's a famous exert by Joe Armstrong, creator of Erlang:
The problem with object-oriented languages is they've got all this implicit environment that they carry around with them. You wanted a banana but what you got were a gorilla holding the banana and the entire jungle.
Actual issue
The banana monkey jungle problem is not a problem of inheritance at all. It is due to bad design of your software. You do not duplicate your code between projects. You can design your object-oriented programs so that instead of classes depending on classes, they depend on interfaces. In other words, instead of accepting a specific class as an argument, they accept any object that implements a specific interface. This will help you transform your classes so that your class isn't dependent on those classes, as well as dependencies of dependencies and so on.
Diamond Problem
The diamond problem occurs in inheritance-oriented languages that support multiple inheritances.
In this case, it is possible to inherit from the same class through multiple inheritance paths, as shown in the figure above.
The diamond problem is an ambiguity that arises when two classes, A and B, inherit from a superclass, and class C inherits from both A and B.
If there is a method in the superclass that A and B have overridden, and C does not override it, then which class of the method does C inherit: that of A or that of B?
Actual issue
It has been agreed that this is indeed a problem of multiple inheritances in OOP. It is a serious problem for languages (like C++) that allow for multiple inheritances of state. To know more about the solution for C++, read this article. In Java, however, multiple inheritances are not allowed for classes, only for interfaces, and these do not contain state.
When you follow a similar pattern of multiple interface implementations, Java warns you if both methods' signatures are the same. Otherwise, you can use the default
keyword to make one method the default option.
You have to design your system in a way that you avoid multiple inheritances or use the necessary workarounds.
Fragile Base Class Problem
A fragile base class is a common problem with inheritance, which applies to Java and any other languages that support inheritance.
In a nutshell, the base class is the class from which you are inheriting, and it is frequently referred to be fragile since modifications to it might have unanticipated consequences in the classes that inherit from it.
I shall use the example used by Charles Scalfani in his article.
import java.util.ArrayList;
public class Array
{
private ArrayList<Object> a = new ArrayList<Object>();
public void add(Object element)
{
a.add(element);
}
public void addAll(Object elements[])
{
for (int i = 0; i < elements.length; ++i)
a.add(elements[i]); // this line is going to be changed
}
}
Consider the above-written code.
Important: Notice the commented line of code. This line is going to be changed later, which will break things.
This class has two functions on its interface, add()
and addAll()
. The add()
function will add a single element and addAll()
will add multiple elements by calling the add function.
And here's the derived class:
public class ArrayCount extends Array
{
private int count = 0;
@Override
public void add(Object element)
{
super.add(element);
++count;
}
@Override
public void addAll(Object elements[])
{
super.addAll(elements);
count += elements.length;
}
}
The ArrayCount
class is a specialization of the general Array
class. The only behavioral difference is that the ArrayCount
keeps a count of the number of elements.
Let’s have a look at both of these classes in detail.
- The Array
add()
adds an element to a local ArrayList. - The Array
addAll()
calls the local ArrayList added for each element. - The ArrayCount
add()
calls its parent’s add() and then increments the count. - The ArrayCount
addAll()
calls its parent’s addAll() and then increments the count by the number of elements.
And all works fine.
Now for the breaking change. The commented line of code in the Base
class is changed to the following:
public void addAll(Object elements[])
{
for (int i = 0; i < elements.length; ++i)
add(elements[i]); // this line was changed
}
From the perspective of the Base
class's owner, it works as expected with all of its automated tests passing.
However, the owner is unaware of the derived class. And the derived class's owner is in for a nasty surprise.
Now, ArrayCount
addAll()
calls its parent’s addAll()
which internally calls the add()
which has been overridden by the derived class.
This results in the count to be incremented each time the derived class’ add()
is called, and then it’s again incremented by the number of elements that were added in the derived class’ addAll()
.
In summary, it is being counted twice.
Since this is a very likely scenario, the creator of the derived class must understand how the Base class is implemented. And they must be kept up to date on any changes to the Base class since they may break their derived class in unexpected ways.
Actual issue
The main challenge with inheritance is the connection between the parent and its children. This relationship makes it difficult for the parent class to evolve without breaking its children. This highlights the well-known problem of concrete classes relying on other concrete classes. Concrete classes should rely on abstractions, according to the SOLID design principles. Abstractions are not (as much) affected by the fragile base class problem.
To avoid the worst of these difficulties, designate all classes as final unless you want to inherit from them specifically.
For those you intend to inherit from, design them as if you were designing an API: Hide all implementation details; be rigorous about what you emit and cautious about what you accept, and thoroughly explain the anticipated behavior of the class.
Conclusion
Senior full-stack engineer Ilya Suzdalnitski published a lively 6,000-word essay, calling object-oriented programming "a trillion-dollar disaster."
Precious time and brainpower are being spent thinking about “abstractions” and “design patterns” instead of solving real-world problems… Object-Oriented Programming (OOP) has been created with one goal in mind — to manage the complexity of procedural codebases.
In other words, it was supposed to improve code organization. There’s no objective and open evidence that OOP is better than plain procedural programming…
Instead of reducing complexity, it encourages promiscuous sharing of mutable states and introduces additional complexity with its numerous design patterns. OOP makes common development practices, like refactoring and testing, needlessly hard…
Using OOP is seemingly innocent in the short term, especially on greenfield projects. But what are the long-term consequences of using OOP? OOP is a time bomb, set to explode sometime in the future when the codebase gets big enough.
Projects get delayed, deadlines get missed, developers get burned-out, adding in new features becomes next to impossible. The organization labels the codebase as the “legacy codebase”, and the development team plans a rewrite….
OOP provides developers too many tools and choices, without imposing the right kinds of limitations. Even though OOP promises to address modularity and improve reusability, it fails to deliver on its promises…
I’m not criticizing Alan Kay’s OOP — he is a genius. I wish OOP was implemented the way he designed it. I’m criticizing the modern Java/C# approach to OOP… I think that it is plain wrong that OOP is considered the de-facto standard for code organization by many people, including those in very senior technical positions.
Precious time and brainpower are being spent thinking about “abstractions” and “design patterns” instead of solving real-world problems… Object-Oriented Programming (OOP) has been created with one goal in mind — to manage the complexity of procedural codebases.
In other words, it was supposed to improve code organization. There’s no objective and open evidence that OOP is better than plain procedural programming…
Instead of reducing complexity, it encourages promiscuous sharing of mutable state and introduces additional complexity with its numerous design patterns. OOP makes common development practices, like refactoring and testing, needlessly hard…
Using OOP is seemingly innocent in the short-term, especially on greenfield projects. But what are the long-term consequences of using OOP? OOP is a time bomb, set to explode sometime in the future when the codebase gets big enough.
Projects get delayed, deadlines get missed, developers get burned-out, adding in new features becomes next to impossible. The organization labels the codebase as the “legacy codebase”, and the development team plans a rewrite….
OOP provides developers too many tools and choices, without imposing the right kinds of limitations. Even though OOP promises to address modularity and improve reusability, it fails to deliver on its promises…
I’m not criticizing Alan Kay’s OOP — he is a genius. I wish OOP was implemented the way he designed it. I’m criticizing the modern Java/C# approach to OOP… I think that it is plain wrong that OOP is considered the de-facto standard for code organization by many people, including those in very senior technical positions.
It is also wrong that many mainstream languages don’t offer any other alternatives to code organization other than OOP.It is also wrong that many mainstream languages don’t offer any other alternatives to code organization other than OOP.
The argument implies that Java is to blame for the adoption of OOP, citing Alan Kay's remark that Java "is the most upsetting thing to happen to computers since MS-DOS."
It also quotes Linus Torvalds' observation that "limiting your project to C means that people don't screw things up with any idiotic object model."
And, in the end, it recommends functional programming as a preferable option, stating the following claims regarding OOP:
- "OOP code encourages the use of shared mutable state, which has been proven to be unsafe time and time again… Encapsulation, in fact, is glorified global state."
- "OOP typically requires a lot of boilerplate code (low signal-to-noise ratio)."
- "Some might disagree, but OOP code is notoriously difficult to unit test… Refactoring OOP code is really hard without dedicated tools like Resharper."
- "It is impossible to write good and maintainable object-oriented code."
This post has some criticisms which are actually true but it has some biased opinions as well. Do give it a read.
Yes, there are a few downsides of OOP as mentioned in the article. But they are mostly due to bad design and poor coding practices. The advantages and benefits of OOP definitely overpower its issues.
Although I do agree that OOP has been oversold as a mainstream idea and it forces us to think - even simple problems, from the object-oriented perspective.
That's it for this article. Hope you learned something from this. If you have any experiences with OOP (good/bad), please feel free to mention them in the comments.
References