My tongue-in-cheek title is a riff on the 1968 Dijkstra essay, “Go-to statement considered harmful.” Dijkstra argued for structured programming, over BASIC spaghetti. Recently one of my friends expressed interest in becoming a computer programmer, so I purchased a copy of Algorithmics: The Spirit of Computing (3rd Edition), 2003, by David Harel with Yishai Feldman, as a gift for my young friend. The second edition of the book (1987) had inspired me, as a novice, with it’s clear and enthusiastic presentation of the essentials of computer theory. I told my friend that if the book didn’t also inspire him, then maybe he would not ultimately enjoy programming.
As I skimmed the third edition, curiously looking at the updates for 2003, I was dismayed to find a well-known mistake presented as an example for Object-Oriented Programming. On page 71, Harel and Feldman introduce inheritance, which ‘denotes the ability of the programmer to define inclusion relationships between classes.’ They proceed to give the horribly flawed example of making a square shape extend a rectangle shape. This is a perfect example of how very intelligent people can misuse inheritance and even teach incorrect usage. I’m going to argue that inheritance is completely unnecessary, and even harmful, due to the extremely high potential for misuse.
The ‘correct’ use of inheritance
Liskov’s Substitution Principle must be followed. The Liskov’s Substitution Principle states that if a program module is using a Base class, then the reference to the Base class can be replaced with a Derived class without affecting the functionality of the program module.
I am not aware of any OO compiler that must enforce Liskov’s principle, although automated code inspection software can report violations of it. Unfortunately, few programmers follow it. So, why can’t a square class be derived from a base rectangle class?
A Square is Not a Rectangle
It seems intuitive. A square is a special case of a rectangle, where the height and width are equal, right? Yes, but, by definition, a rectangle has a height and width that can be different. A rectangle class will have height and width fields, which could also be mutable.
Harel and Feldman say, “From the point of view of the programmer, squares can handle all messages defined for rectangles…” They are wrong! If a square object receives the message setHeight(20), does it also set the width? If so, it is already confusing, and if not, then if the width is not the same, it is no longer a square. It makes no sense for a square to have height and width fields. A square is more constrained than a rectangle. Hence it cannot be Liskov substitutable with a reference of type Rectangle. A better idea for an abstract base class for Square might be RegularPolygon, all of which have a number of sides and a length of side. The method getInternalAngle() would be a fine abstract method for such an abstract base class. It would also be a good idea to make the number of sides immutable! Square could extend RegularPolygon, and could have a constructor taking only lengthOfSide, and internal fixing the number of sides to four and return 90 degrees from getInternalAngle(). But now you begin to see all the considerations involved in making a class designed for inheritance. I’m not even going to talk about the diamond inheritance problem encountered in languages like C++ which have multiple inheritance. In any case, it’s easy to see that a square is not a rectangle. The Is-A relationship is violated.
Inheritance isn’t needed at all
It’s common to find the advice, ‘prefer composition to inheritance’, given to OO programming students. Well, then why even have inheritance at all? Simply because we can! Somebody thought of it. And somebody thought of GO TO, as well. There is no programming problem that requires an Object-Oriented language, let alone inheritance. As Harel and Feldman are keen to point out, all programming languages are equivalent. The differences are ‘pragmatic’.
It comes down to the fact that languages are chosen for their applicability to specific problems, but also for their aesthetic appeal. A book called Beautiful Code came out a few years ago, but it got mixed reviews and I can’t comment on it, having not read it. The idea, though, is that a programming language has to express the ‘theory’, the mental model, that the programmer is building. It has to support abstractions for data structures and behavior (algorithms). Harel and Feldman ask (3rd ed., p. 58), Why Not an Algorithmic Esperanto? Why not have just one programming language for everything? As one answer to that, all you need to do is look into Domain-Specific Language (DSL). A DSL is a purpose built ‘language’ syntax, usually based on an underlying dynamic language, such as Ruby, for simplifying a specific task. If there are hundreds of languages, there may be thousands of DSLs. Each DSL improves the speed at which the specific programming task can be completed, reducing labor, increasing quality, blah, blah, blah. Aesthetics are marshaled in support of productivity. This is why you need to learn new languages and DSLs. It’s pragmatic.
If you’re pragmatic, you will ‘prefer composition to inheritance’. But why not simply not use inheritance at all? In an OO language, then you’ll avoid all problems of masked (hidden) fields, overridden methods, and deep inheritance hierarchies. When choosing frameworks, sniff out inheritance in the libraries. Are you forced to use it to adopt the framework. Move along to the next one. Why is this pragmatic? Because it will simplify one of the many dimensions along which complexity can develop.
Mutable state considered harmful, too
Inheritance is one of the problems for OO, but mutable state is perhaps worse. Inheritance can impact dynamic runtime behavior (by virtual method selection). But shared, mutable state has become a serious problem for concurrent programming. I’ve previously posted about the challenges of concurrency on the Java Virtual Machine. So, now, why even use in-memory shared mutable state? In fact, sharing mutable state in persistence systems (file systems, databases) is no longer a good idea, either. How about just write and read data? Imagine how much is to be gained by immutable data. It is much easier to reason about values that don’t change over time (and possibly even carry time stamp information), and to write correct programs that assume no mutable data.
We’re stuck with OO languages and compilers, as much as banks are stuck with COBOL. However, that does not mean we have to keep using the ‘bad parts’. And yet, it seems unavoidable. Because there still exist too many bad examples. And if such bright authors as Harel and Feldman can repeat such a flawed example as ‘square derived from rectangle’, who can help us, but ourselves?