It’s time to send the pendulum back the other way, says long-time programmer and author Cameron Laird. Object orientation is a good thing — but only when used in moderation.
I don’t like a lot of what’s called object-oriented programming (OOP). More to the point, it appears that common OOP practices don’t help the developers who create and maintain source code.
OOP is a good thing — in moderation. But too much OOP nowadays indulges in excess. Let’s look at how to use it in healthy balance with other development fundamentals.
We developers have barely begun to analyze development of software in a rigorous way. We have plenty of folklore —
GOTO is bad, type-checking is good, code inspections work — but we have only started to learn how to judge such claims objectively, let alone perform the necessary evaluations. Object orientation is “advanced” in that it emerged in recognizable form in the mid-’60s, well after such procedural stalwarts as COBOL, Fortran, Lisp, and Algol were established. Smalltalk demonstrated significant commercial OOP success, but it was only in the ’90s that C++ and Java built on Smalltalk’s example to make OO “mainstream.” Through all this time, though, OO has proven itself more as a fashion acquisition and organizational habit than an engineering improvement. Many arguments, at least a few experiments, and occasional parodies, undercut OO’s claim to a “scientific” basis.
The first lesson to learn about OO, then, is prudent skepticism. Much of its undeniable marketplace success looks more like the (passing) adoption of corsets in Elizabethan England, than, say, the irreversible and little-regretted transition from typewriters to word processors.
Even when we have reservations about OO’s overall impact, we can, as individual practitioners, investigate how best to style our OO code. That’s the ultimate goal of this here: specific idioms to use or avoid to make OO source code as effective and maintainable as possible.
One fundamental aspect of OO expressiveness is class definitions in terms of inheritance. Some designers aim for inclusiveness and deep structure; they produce class hierarchies where everything is richly structured in terms of inheritance from carefully constructed base objects.
I see a profound mistake in this, if a well-motivated one. Schoolroom exercises simplify real-world situations so that classes have only a few, simple relations to each other. This leads to the habit of thinking that class definitions must have logical relations to each other. Commercial coding deals with far messier situations, where the relationship between
CustomerEvent is ambiguous, at best. Many times, it’s wisest simply to leave a relationship unbound.
Another part of inheritance complication is a misapplication of DRY. “Don’t Repeat Yourself” [DRY] is perhaps one of the most fundamental programming principles, and it deserves to be. Alert designers and coders note that two classes share an attribute or method, and wisely look to abstract that commonality. The problem arrives in the exclusive reliance on inheritance to express the relation.
Inheritance certainly is a way to enforce commonality. We might, for example, sub-class everything designed to show up on a piece of paper from
Printable — and I see designs that are that misguided. Inheritance is not the only way to express commonality, though.
The wisest OO commentators are unified on this point: Inheritance should be reserved for is-a relations. While OO languages are capable of inclusive use of inheritance, where everything that is a relation is modeled with inheritance, that’s a mistake. Inheritance should be exclusive, confined to the relatively few is-a relations that occur naturally in any domain.
This leads to “separated” class hierarchies, with multiple base classes. This style receives little attention in academic settings, yet it’s the right one to use in effective real-world software development.
DRY remains important, and such techniques as has-a analysis, duck typing, aspect-oriented programming, and generic types deserve as much attention in your programming as conventional inherited class definition.
We’ve been through this sharpening of focus before. For example, the arrival of “desktop publishing” was exciting. For the first time, it was practical to manage multiple fonts and other typographic treatments in low-end documents. But DTP taught us (or most of us, sigh) that just because we can announce the company picnic in eight different font families, with six different colors, doesn’t mean we should. Clean style is more about keeping only what can’t be replaced than what might do a remote good.
That’s what I mean by “exclusive” versus “inclusive” class modeling. No matter how much deep ontological research into an is-a diagram tempts you, it’s almost certain to be wasted effort. Is-a relations end up propagating their fragility to your design as a whole. Identify the is-a parts that are unmistakable, then combine them through wise duck typing, interface definitions, or a similar technique to make a better balanced, more robust, and still DRY-observant overall design.
There’s actually another step to exclusive-versus-inclusive modeling, one often neglected because it’s mistakenly regarded as merely technical: Liskov substitutability. is-a is about class definitions; Liskov substitution governs definitions of the attributes and method signatures within a class. Respect for Liskov substitution avoids unpleasant encounters with the contra-variance problem.
At an architectural level, one of our targets for good design should be the Open-Closed principle, which Liskov substitution enables.
For me, the most interesting questions in software development are less like, “Is OO good or bad?” and more like, “How do we make the best of OO?” The next steps we need with OO are clear:
- Class diagrams need to be so simple and shallow that no one would argue there are definitional errors, not as deep and rigid as someone might argue is correct.
- It’s time to re-study all of Bob Martin’s SOLID principles.
We’ve come as far as we can with naive approaches to overstuffing all of an application domain into a class hierarchy (what Steve Yegge calls, in one of the references above, “noun-oriented thinking”). A better balance between OO’s different aspects will give us more robust and satisfying designs and implementations.