What we don't need in object-oriented programming
Once I heard Alberto Brandolini giving a keynote at an Italian conference, saying, between other insights, that Lego bricks where one of the most abused metaphors in software engineering.
One of the most abused sayings, instead, is this one:
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. -- Antoine de Saint-Exupérythat is often used in reference to application design. However, it is in general true that you should strive for writing code as simple as possible (in length but in complexity too) to obtain an end result specified by a requirement or a user story. If there are two designs for a feature, almost always we would choose the simplest.
This article takes a broader view on object-oriented programming, and ask the following question:
- What are the constructs we don't need in an object-oriented language? What we can take away without hampering our capabilities of building software?
- If we actually don't need them, we can eliminate them from the language, and make it more simple. At the same time, make the application written in it more consistent, since they have less features to abuse.
Forgive me if I'll refer to patterns instead of code, but I don't want this discussion to become language-specific.
Usually, when you check for an object being instance of a class, you can implement the scenario with polymorphism.
Should I say anything? Even Java, the most-diffused object-oriented language in the world, has Goto as a reserved keyword (I hope it is not implemented).
break and continue (disguised jumps)
They are really gotos which have put on camouflage, and any professor of computer science would keep an eye on you if you used them (mine did, and I was too young to recognize the value of clean code).
Subclassing is very often abused, with classes extending other ones only to gain access to their methods, in spit of is-a relationships; maybe we can prohibit them altogether. We'll gain almost automatic conformance to the Liskov Substitution Principle, since it would involve only interface and their implementations. The main reason for its infringements, sharing methods code, would go away.
Since eliminating subclassing is a bit extreme, an idea that stroke me while re-reading Uncle Bob's explanation of SOLID principles is to eliminate subclassing of concrete classes. This way, we'll never introduce a dependency towards an implementation detail, but we can still make use of abstract classes, very valuable in an API.
protected (subclassing scope)
Of course, if we eliminate subclassing, we can throw away the intermediate protected scope altogether. Also if we choose a single level of subclassing towards an abstract one, this scope would do less harm than it does today.
For example, in Zend Framework almost always is protected instead of private, and the user is free to subclass and access any internal part of the framework's components. The scenarios that happen then are two: either the framework developers cannot modify code anymore to avoid breaking backward compatibility (so why they bothered restricting the scope in the first place), or the user has to revise his classes when upgrading from 1.6.1 to 1.6.2 because of sudden changes.
public (for fields)
Public fields are the death of encapsulation, and I can't remember the last time I introduced a public field long time ago. Let's just throw them away.
private (for methods)
How do you test private methods? Often we arrive to a situation where we have complex private methods we want to test independently. This is often a smell of a class that does more than one thing (Single Responsibility Principle), and should be ripped up. In this case, we can move the private methods away and transform them in public methods on a private collaborator, safeguarding encapsulation but simplifying the picture and being able to test them due to the new contract established between the main class and the extracted one.
switch (conditional chain)
Switch chains, especially if duplicated in different places, can be replaced with polymorphism. The idea is to push the switch up to che choice of the object, and push the particular operations in each of the switch branches into the chosen object.
If we can replace switches, we can also replace ifs, that are a special case of switch with only two branches. The reasoning is the same and the advantages are clear: you would have only one execution path to test for each method, and you'll be sure of which lines are executed: all of them. Misko Hevery says that often most of the ifs can be replaced with polymorphism, but you should be pragmatic about it and not try to kill them all.
The problem with the last two elimination is that in our languages we can't remove all ifs yet, but only push them up in the creation of the object graph. But if we had a language or a construct capable of transforming textual configuration into a graph of objects, we could get rid of ifs even in the Factories. Dependency Injection containers actually transform configuration into objects wired in different ways, so we may be near to a solution.
Some of this crude eliminations are extreme and maybe not even practical. However, before making a trade-off between two scenarios, a programmer has to know the extreme situations that arise from the overuse and absence of a construct or feature. I am the first to recognize to have committed (in both senses) if and subclassing abuse. I hope to have made you think a bit about subclassing, scopes and conditionals, so that the next time you start coding you will ask yourself: I really need to use yet another subclass / a very long private method / this duplicated switch construct?