Dr. Carlo Pescio|
Systematic Object Oriented Design
Published in IEEE Computer, Vol. 30 No. 9, September 1997.
Over two decades ago, expert systems were one of the most promising
technologies. Then researchers rapidly stumbled into what's called
the Feigenbaum Bottleneck: as the complexity of the domain
grows, it becomes very difficult for human experts to formulate
their knowledge as practical strategies (human say-how).
It is reasonably easier to demonstrate knowledge by doing something
(human show-how); most peoples can also make good choices
between alternatives (human say-what).
Not much attention has been paid to the Feigenbaum Bottleneck outside the domain of artificial intelligence. This is unfortunate, because the bottleneck hints at the inner nature of human beings. To see the depth of its effects, we have to look no further than the current state of software systems design.
Principles, principles everywhere
Early in the history of programming, brilliant peoples realized that every good software system has some desirable properties: It should be extendible; parts of it should be modifiable without major impact on other parts; and so on.
Because of the Feigenbaum bottleneck, it is very hard to find precise, step-by-step instructions to build systems with such properties. It is easier to articulate the desirable properties under the form of design principles.
Over the years, the wealth of knowledge accumulated as design principles has reached a critical mass. Entire books are now dedicated to the subject. Still, despite of this body of knowledge, design remains difficult. A major problem in principles-driven design is the ambiguous and non-constructive nature of most principles. Ambiguity comes from the use of natural language, but also from the desire to capture a large number of cases under a single principle. As a consequence, adherence to a principle is often non-measurable. Words like "abstraction", "detail", "extensible", "open" are very expressive, but lack the necessary precision.
Being non-constructive means that a principle often provides no guidance toward a design which respects the principle itself. This is also common in mathematics: a theorem may guarantee that something exists, but may not give any hint about how to find it.
Together, these two problems make principles-driven design somewhat hard to practice with success without a considerable experience.
The revenge of practice
In more recent times, design patterns have emerged as a valid alternative to the principles-driven approach. The introductory chapter of the GoF book is adamant: "One thing expert designers know not to do is solve every problem from first principles. Rather, they reuse solutions that have worked for them in the past".
If principles represent the "say-what" approach to design, patterns are the "show-how" way. You look for recurring design problems, find the solutions that worked better, and capture their essence in a pattern.
The biggest problem with patterns is that they rely on previous experience, so when the available knowledge does not cover your problem, you are out of luck. Sure, human beings are fairly flexible; they can come up with some workable solution anyway. But obviously, there is a limit to how far you can go with patterns alone. Also, patterns leave a lot of details unspecified, so they are independent from each instance. As a consequence, the designer must be able to fill in the blank, usually backing up with its own experience, which is hard for inexperienced designers.
Finally, there is a "disregard for originality" in the pattern movement. Stop and think about how those clever solutions have been devised the first time. Because in absence of previous experience, the original, bright designer didn't use patterns, but some other approach which is now intentionally neglected. Patterns are a great way to encode knowledge, but as a design technique they are necessarily incomplete: they leverage existing solutions but cannot generate completely new ones.
It is recognized that we lack a systematic approach to design. Assuming we have done a good analysis job, we are still largely left at the mercy of experience. Our own experience, to disentangle ourselves in the jungle of principles, and other people's experience, under the form of patterns. This being the same in all the other disciplines, we may conclude that it is an unfortunate fact of life, shrug our shoulders, and go back to our high-paying design jobs. But this is not generally true. Electrical engineering, for instance, has a large theoretical background but also a comprehensive body of heuristic techniques that can be systematically applied to solve problems. The same holds for other fields as well. And here we close the circle: the Feigenbaum bottleneck notwithstanding, we have to bite the bullet and try to articulate a "say-how" approach to software design.
A transformational approach
Trivial design is easy. After all, is just a matter of laying down a solution, and most peoples can even skip this step altogether and jump to coding. In both cases, the result is often less than optimal.
However, if we have a systematic method to recognize and solve common problems in the initial, trivial design, we have a much higher chance of ending up with a better result. Also, if the problem-solving approach is constructive, we can gradually learn how to avoid problems up front, applying the techniques on the fly as expert designers do.
Here I describe an approach to design based on transformation techniques, which I've used with success in the development of large systems.
To find a transformation technique, you start with an informal design principle. Then, you try to distillate some necessary conditions which are unambiguous and amenable to formal verification. It is not necessary to capture the whole essence of the principle in a single rule: in some cases, that's simply not possible. However, we can settle for a small number of necessary conditions, provided that they can be conceptually ascribed to the principle, and that they are formally verifiable.
The next step is to identify a set of transformations which can be used to enforce the observance of a rule. Each transformation technique, when applied to an interoperating set of classes that violate a design rule, should lead to an equivalent structure that respects the rule. Patterns are a good source of inspiration for atomic transformation techniques, because most patterns are based on a combination of several simpler techniques. That's why patterns aren't always easy to understand "in depth".
Consider the informal principle "abstractions should not depend upon details". I have taken this principle and identified a formal necessary condition and some transformation techniques [Pes98]. Using those techniques, I derive the Observer and the Mediator patterns from two trivial designs.
Here, I briefly consider a simpler case, the "I'm Alive!" principle [CN93]. This principle is usually spoken as "an object should expose services, not data". This is a fundamental OOP principle, but unfortunately is not based on measurable semantics. However, in many practical cases a violation of the principle follows this scheme: object A asks object B for one of its parts (C), then does something with C (see fig. 1).
For instance, B may be a Book, C a Chapter, and A may iterate over the pages and print them. If we consider only this scheme, it's much easier to come up with a good design rule which can be automatically verified. In fact, we don't even have to invent the rule, as it already exists. It's called the Demeter Law [LH89] and it states that "any method M of a class C should use only the immediate part of the object, the parameters of M, objects that are directly created in M or global objects" (other formulations are more amenable to automatic checking). Basically, the Demeter Law prevents from taking a part of another object and doing something with it. Any program can be modified to conform with the Demeter Law, but automatic transformations rarely yield good code. What we want is to find a set of transformations which respect the spirit of the rules, not only the letter. Our job is to find the verifiable condition, and to provide a set of transformations to the designer. The designer picks the best suited transformation.
Figure 2 gives an example of a (simple) transformation: now A asks B to do something, and B gets its own part and asks it to do it. Instead of asking for a chapter and then print it, now A asks to print a certain chapter. Each object exposes only services, not data (the "get" becomes "do").
Although trivial once exposed, this transformation rule is useful in a large number of cases, and is used instinctively by experienced designers. Naturally, there are several other transformation techniques which may be used to enforce the Demeter Law, as well as other measurable, necessary conditions that subsume the "I'm Alive!" principle. If you know some interesting techniques, I'll be glad to hear from you.
Over the years, I've identified a small (but continuously growing) set of verifiable rules and transformation techniques. I've applied them during the design of large, real-world critical systems, and taught them to developers. I've found that they are very easy to grasp and that people end up paying more attention to design principles once they know how to verify and make correction to their works. Obviously, many rules and techniques are yet to be found. Interestingly, verifiable rules may lead to some true object oriented CASE tools, beyond glorified drawing systems.
[CN93] Peter Coad, Jill Nicola, "Object Oriented Programming", Yourdon Press, 1993.
[LH89] K. J. Lieberherr and I. M. Holland, "Assuring Good Style for Object-Oriented Programs," IEEE Software, September 1989.
[Pes98] Carlo Pescio, "Deriving Patterns from Design Principles", Journal of Object Oriented Programming, October 1998.
Carlo Pescio holds a doctoral degree in Computer Science and is a consultant and mentor for various European companies and corporations, including the Directorate of the European Commission. He specializes in object oriented technologies and is a member of IEEE Computer Society, the ACM, and the New York Academy of Sciences. He lives in Savona, Italy and can be contacted at firstname.lastname@example.org.