This is the counterpoint to the article Modeling Complex Behavior Simply by Stephen J. Mellor that appeared in ESP March 2000. Read Mr. Mellor’s reaction, “The Last Word”, below.
“By relieving the brain of all unnecessary work, a good notation sets it free to concentrate on more advanced problems, and, in effect, increases the mental power of the race.”
Anyone who has built toy houses out of Lego blocks can appreciate the inherent simplicity of the basic concept (although it is well worth noting that the variety and sophistication of Lego blocks is steadily increasing).1 But we all know that it isn’t possible to build real houses, let alone modern skyscrapers, using scaled-up Lego blocks. When things get larger, certain effects emerge, and aspects that can be safely ignored at a smaller scale gradually become more significant. In tall buildings, for example, the ability to sustain high winds is a major design consideration, as is the need to maintain water pressure at the higher floors or the need to transport large numbers of people efficiently.
Today’s engineers are frequently being asked to construct large-scale systems whose complexity exceeds the comprehension capacity of the individual human mind. That proverbial technological wonder, the modern jumbo jet, has over six million parts. Many current software systems consist of millions — even tens of millions — of lines of high-level language code. While some have argued (not without merit) that we should refrain from building systems on this scale, for better or worse, our society has made that choice. Consequently, extreme complexity is innate to many modern engineering problems and we must devise means for taming it.
Is making our tools more primitive the right answer? In the history of software technology, many attempts have been made to devise simple yet effective programming languages (for example, BASIC). Of those, the only ones that have not been marginalized are ones that, like Lego blocks, diversified and became more sophisticated (say, Visual BASIC). Innate complexity is like an inflated balloon: squeezing it at one end simply forces the rest of it to expand. Similarly, using naïve programming constructs for complex systems only passes the complexity buck to the programmers who are forced to write more code to realize the required functionality (thereby increasing the likelihood of errors and overall development time). We cannot cheat our way out of complexity; our only hope of coping is to raise the level of abstraction, not to lower it.
The real issue is not how we can simplify UML — that is easily done — but how can we simplify the complex tasks for which UML is intended.
In contrast to past attempts at unification (notably the language PL/I), UML was designed with this apparent dilemma in mind. The core language defined by the UML standard consists of universal abstractions, such as the notions of class and classification, association, object, or interface, all equally applicable to any domain or technology. These general concepts are well suited for high-level modeling as well as for detailed modeling in the case of simpler applications. However, if it becomes necessary to express sophisticated domain-specific nuances, UML provides a set of specialization (“extensibility”) mechanisms that allow it to be customized to the case at hand.2 For example, in UML, concurrent objects can be represented using the “active” object concept. But UML doesn’t specify the detailed semantics of that concurrency, in recognition of the fact that different domains may place very different demands on their concurrency model (for instance, the granularity of concurrency, priority model, or scheduling policy).3
A set of domain- or application-specific specializations of the general UML semantics are typically packaged together in something called a “profile” to be used by those who need it and safely ignored by others. In this way, UML avoids the undesirable “language bloat” syndrome or the need to trade off the conflicting needs of different users. Yet, because a profile is merely a specialization of the universal UML concepts, even someone unversed with a particular profile will still have a general understanding of a model based on that profile.
But if the extensibility mechanisms of UML allow the meaning of the core constructs to be changed, as Mr. Mellor suggests, how can we trust our general understanding of these concepts? In that case, does the standard have any real value? The fact is that the extensibility mechanisms cannot be used to arbitrarily change the meaning of an existing UML construct. By definition, a profile must preserve the original meaning of a construct, although it may tighten its semantics for a particular purpose. For instance, it may define a particular scheduling policy. This is simply an example of “genericity” — a well known and common technique for simplifying complexity.
How does UML dynamic modeling stack up?
First, let’s model the essential acceleration/deceleration mechanism. This can be done in several different ways, but let’s settle on the simple state machine shown in Figure 1.
Of course, this state machine only applies to the case when the cruise control is “on.” Mr. Mellor argues that the acceleration/deceleration mechanism should be kept separate from the on/off button to decompose the problem into lesser problems. However, regardless of where the change of state of the on/off button is detected, the “off” event still has to be handled by the acceleration/deceleration state machine or it will never turn itself off. So, we still have to add some more elements to the basic state machine to deal with this situation, as shown in Figure 2.
Note that we have to account for the cases where the off button was pressed while the system was accelerating or decelerating.
Although this adds a bit of complication, plain old “flat” state machines can seemingly still do the job, and we don’t seem to have any real need for hierarchy.
But, let’s now make this example even more realistic. The cruise control system on my car has, in addition to the on and off buttons, a number of other controls, including a “resume” button, a “suspend” button, and several other controls which I will omit in this discussion. When the car is first turned on, we may need to run through a diagnostic test of the system. As a safety measure, we may also have to deal with the situation where some error may be detected within the system. This, of course, can happen at any time, even as the car is operating. A reasonable attempt at a state machine that deals with these additional requirements is the example shown in Figure 3.
Our diagram is obviously becoming more complicated and more difficult to comprehend. Yet it’s still incomplete. For example, it doesn’t show the numerous “no-op” self-transitions attached to every state, which occur when, say, the off button is pressed in the “off” state or the suspend button is pressed in the “failed” state. Worse yet, imagine what would happen if we were a little more refined in our error-handling policy and decided to differentiate between transient errors and permanent ones (for example, a transient error might force the system back to the “diagnosing” state.) This would require yet another transition from every one of the states in the state machine. The result isn’t only added complexity, but a higher likelihood for errors.
What we are seeing here are the standard scale-up difficulties of simple “flat” state machines in real-world situations (although cruise-control engineers probably consider the above model far too simplistic -these things tend to be much more complicated with many more states and inputs than I’ve shown here).
Misuse of hierarchy? Now, let’s see how we can represent this exact same behavior using hierarchical state machines. The idea of hierarchy is an age-old technique for taming complexity -it allows us to comprehend the forest in spite of the trees. Programmers quickly realized its value and took advantage of it; initially by using the basic procedure construct (just imagine trying to program without it!) and later by adding further specializations such as packages, modules, layers, and so on. Note that each of these forms serves a different purpose, even though they are all based on the same underlying principle. A misguided emphasis on simplification might induce us to reduce them all to just one multi-purpose construct.
In our cruise control example, at the most abstract level, we can clearly see how the entire mechanism is put into or taken out of service, unburdened by the detail of suspends/ resumes or accelerations/decelerations. Refer to Figure 4. The icon in the lower right-hand corner of the “on” state indicates that this is a hierarchical state machine. Looking inside the “on” state, we see the details of this behavior, as shown in Figure 5.
Of course, with well-designed tools we could see the entire state machine as a single collapsed view. (Note that even this complete view is simpler than the “flat” state machine view, since many individual transitions are nicely collapsed into group transitions.)
This graduated descent into ever-increasing detail is really no different than what we experience upon reviewing a well-structured program. Only bad programmers put everything into the main program.
But isn’t it possible to get the same hierarchical effect using structural decomposition, as Mr. Mellor suggests? For example, can we not delegate the behavior in the “on” state to a separate component? Not really. As I have already argued, the ”on” component still needs an additional “off” state. The same holds for the “failed” and “suspended” states. Structural decomposition in this case would just lead to the same state being defined in multiple places, with the resulting additional headache of keeping them all in sync. We wouldn’t end up with more manageable behavior, but the exact opposite.
Overly complex interactions? This is a reference to the interactions that occur between various UML modeling concepts. Mr. Mellor suggests that there is such a large variety of these that it isn’t possible to keep it all in one’s head. He backs his claim using an emotional argument, asking us whether we would be willing to “stake our lives” on every member of the development team sharing a common understanding of these concepts. Notwithstanding the fact that most people are unwilling to stake their lives on anything, this argument may also appeal to our modern-day penchant for quick fixes. Technology is changing so rapidly these days that there is an understandable reluctance to invest too heavily in learning anything in depth. How many of us take the time to properly read the documentation that arrives with some new software? (Unfortunately, not everything can be made intuitively self-evident, no matter how clever the user interface techniques.) Fortunately for us all, professionals working on safety-critical systems do take the time to thoroughly learn their tools.
There are indeed interactions between certain UML features and, in some cases, we may have to consult a reference manual — a common practice in most modern professions. For Mr. Mellor’s examples, the UML standard answers each of the questions that he raises.
Still, is the net result “overly complex”? Such a claim is hard to quantify, which makes it a matter of personal taste. As a rough guide, the most popular introductory text to UML, by Martin Fowler, describes practically all of the concepts of UML (including state machine concepts) in just 185 pages.5 We can rightly argue that this is not a meaningful measure of complexity, but it is no less meaningful than the page count of a UML reference manual. (Reference manuals, after all, are supposed to be detailed — we never complain about our dictionaries having too many entries.)
Another criticism made by Mr. Mellor under the same heading is that the interactions of UML state charts are “software-specific” in some way and that the “embellishments” of UML state charts aren’t required for hardware. However, the fact is that hardware designers have been using these embellishments with great effect, ever since the inception of state charts. While the UML standard does include additional explanations of how state chart semantics are to be interpreted in case of software systems, this doesn’t imply that they are only meant for software modeling.
Mr. Mellor implies that a single, simpler state machine formalism would be adequate for modeling both software and hardware. This point overlooks the fact that significant semantic differences exist between the two domains, such as the difference between physical concurrency and pseudo-concurrency (software multitasking), how information is propagated, how systems respond to events, and so on. In UML, such differences are accounted for through semantic variation points, which means that each domain can specialize the semantics for its needs.
Too many ways to say the same thing? This, too, is an argument that is difficult to quantify because it is related to that ill-defined property called “expressiveness.” As Mr. Mellor himself explains, expressive power is a continuum that, in the case of modeling computer software, ranges from Turing machines upwards. We mentioned earlier that no absolute optimal point exists in this continuum; the choice depends on a variety of factors.
For example, since UML was designed to be a full-cycle language, it’s even possible to use it as a programming or implementation language. (An implementation language, of course, must have precise semantics, so we have to choose specific refinements of some of the more general mechanisms, such as the scheduling policy. This is achieved readily with a profile.) In that case, the ability to express nuances suddenly makes a world of difference. When it comes to implementation, the pragmatic difference between polling and simply waiting for a notification becomes significant.
The “translation” approach advocated by Mr. Mellor suggests that nuances may be better captured within a translator. Like “simplification,” it sounds like a good idea in the abstract. Yet there are very good technical and pragmatic reasons why we don’t use this technique today with our current programming language compilers. However, this is a completely different topic that warrants its own point/counterpoint discussion.
Yes, in principle we can make do with just a while construct, but that simply means that we need to jump through hoops (loops?) to achieve the equivalent of an until construct. Ultimately, at some level of consideration, they aren’t quite the same thing.
Subset, superset, or none of the above?
The “radically reduced” subset that Mr. Mellor recommends may be the right choice in certain situations. But imposing it on everyone for all situations will simply lead to its rejection by most users. The opposite, “superset” approach, in which different domain specializations are jammed together in a single package, isn’t the right one either — as the PL/I experience has shown. Instead, UML has taken a novel tack: define a relatively small number of universal constructs with generic semantics. These can then be subset and specialized as necessary to fit specific needs. Rather than stretch or amputate our users’ limbs, let us adjust the bed instead.
1. Interestingly enough, this expanding diversity of Lego blocks (around 2,000 different types) hasn’t led to complaints by users that there is too much choice — on the contrary, most of them are quite pleased because they no longer have to resort to hacking or splicing stock pieces to get the shape they need.
2. In earlier modeling languages, users were forced at some point to abandon their high-level models and switch over to a programming language. This discontinuity and the semantic gap between the high-level modeling language and programming languages was a common source of grief.
3. Just imagine the degree of user indignation (and rejection) had UML mandated a specific scheduling policy.
4. “The difference between theory and practice is much smaller in theory than it is in practice” (Anonymous).
5. Fowler, Martin. UML Distilled: Applying the Standard Modeling Language, Second Edition. Reading, MA: Addison-Wesley, 1999.
The Last Word
It seems that we agree: we should define a small number of universal constructs with defined semantics. These can be subset and specialized as necessary to fit specific needs.
The way to do this, though, is to build from a well-defined set of components, starting with the subset. To learn more about how this can be achieved, see ftp://ftp.omg.org/pub/docs/ad/99-12-25.doc. Here you’ll find a response to a request for information made by the Object Management Group regarding a new, improved UML 2.0.Persuasive counterpoint, though. I began to dislike using flat state machines myself, until I remembered that my argument involved partitioning into multiple smaller state machines.
Bran Selic is the vice president of technology at ObjecTime Limited. He has over 20 years of commercial real-time software development and management experience (in telecommunications, aerospace, and robotics), and over a decade of experience with object-oriented design and programming. He is the principal author of Real-Time Object-Oriented Modeling (John Wiley & Sons, 1994), co-authored with Garth Gullekson and Paul T. Ward.
|Figure 1: A Simple State Machine|
|Figure 2: The Basic State Machine With Some Added Elements|
|Figure 3: A State Machine That Deals With Additional Requirements|
|Figure 4: A Simplified Cruise Control Flow Chart|
|Figure 5: Details Of The “On” State|