The limitations of the UML state diagrams
Statecharts have been invented as "a visual formalism for complex systems,"1 so from their inception, they have been inseparably associated with graphical representation in the form of state diagrams. However, it is important to understand that the concept of HSMs transcends any particular notation, graphical or textual. The UML specification2 makes this distinction apparent by clearly separating state machine semantics from the notation.
However, the notation of UML statecharts is not purely visual. Any nontrivial state machine requires a large amount of textual information (e.g., the specification of actions and guards). The exact syntax of action and guard expressions isn't defined in the UML specification, so many people use either structured English or, more formally, expressions in an implementation language such as C, C++, or Java.10 In practice, this means that UML statechart notation depends heavily on the specific programming language.
Nevertheless, most of the statecharts semantics are heavily biased toward graphical notation. For example, state diagrams poorly represent the sequence of processing, be it order of evaluation of guards or order of dispatching events to orthogonal regions. The UML specification sidesteps these problems by putting the burden on the designer not to rely on any particular sequencing. But, when you actually implement UML state machines, you will always have full control over the order of execution, so the restrictions imposed by UML semantics will be unnecessarily restrictive.
Similarly, statechart diagrams require a lot of plumbing gear (pseudostates, like joins, forks, junctions, choicepoints, etc.) to represent the flow of control graphically. These elements are essentially the old flowchart in disguise, which structured programming techniques proved far less significant a long time ago. In other words, these elements of the graphical notation do not add much value in representing flow of control as compared to plain structured code.
This is not to criticize the graphical notation of statecharts. In fact, it is remarkably expressive and can scarcely be improved. Rather, I want merely to point out some shortcomings and limitations of the pen-and-paper diagrams.
The UML notation and semantics are really geared toward computerized design automation tools. A UML state machine, as represented in a tool, is a not just the state diagram, but rather a mixture of graphical and textual representation that precisely captures both the state topology and the actions. The users of the tool can get several complementary views of the same state machine, both visual and textual, whereas the generated code is just one of the many available views.
UML state machine semantics: an exhaustive example
The very rich UML state machine semantics might be quite confusing to newcomers, and even to fairly experienced designers. Wouldn't it be great if you could generate the exact sequence of actions for every possible transition so that you know for sure what actions get executed and in which order?
In this section, I present an executable example of a hierarchical state machine shown in Figure 2.11 that contains all possible transition topologies up to four levels of state nesting. The state machine contains six states: "s," "s1," "s11," "s2," "s21," and "s211." The state machine recognizes nine events A through I, which you can generate by typing either uppercase or lowercase letters on your keyboard.
All the actions of this state machine consist only of printf() statements that report the status of the state machine to the screen. This complete example with source code is available from the companion Website for the book "Practical UML Statecharts in C/C++, Second Edition" at www.state-machine.com/psicc2. The executable console application for Windows is located in the directory <qp>\qpc\examples\80x86\dos\tcpp101\l\qhsmtst\dbg\. The name of the application is QHSMTST.EXE.
Figure 2.11: Hypothetical state machine that contains all possible state transition topologies up to four levels of state nesting
Figure 2.12 shows an example run of the QHSMTST.EXE application. Note the line numbers in parentheses at the left edge of the window, added for reference. Line (1) shows the effect of the topmost initial transition. Note the sequence of entry actions and initial transitions ending at the "s211-ENTRY" printout. Injecting events into the state machine begins in line (2).
Every generated event (shown on a gray background) is followed by the sequence of exit actions from the source state configuration followed by entry actions and initial transitions entering the target state configuration. From these printouts you can always determine the order of transition processing as well as the active state, which is the last state entered. For instance, the active state before injecting event G in line (2) is "s211" because this is the last state entered in the previous line.
Figure 2.12: QHSMTST.EXE example application running in the command window. The line numbers in brackets to the right are added for reference.
Per the semantics of UML sate machines, the event G injected in line (2) is handled in the following way. First, the active state ("s211") attempts to handle the event. However, as you can see in the state diagram in Figure 2.11, state "s211" does not prescribe how to handle event G. Therefore the event is passed on to the next higher-level state in the hierarchy, which is state "s21." The superstate "s21" prescribes how to handle event G because it has a state transition triggered by G. State "s21" executes the actions associated with the transition (printout "s21-G;" in line (2) of Figure 2.12).
Next the state executes the transition chain that exits the source state configuration and enters the target state configuration. The transition chain starts with execution of exit actions from the active state through higher and higher levels of hierarchy. Next the target state configuration is entered in exact opposite order, that is, starting from highest levels of state hierarchy down to the lowest. The transition G from state "s21" terminates at state "s1." However, the transition chain does not end at the direct target of the transition but continues via the initial transition defined in the direct target state "s21." Finally, a new active state is established by entering state "s11" that has no initial transition.
In line (3) of Figure 2.12, you see how the statechart handles an internal transition. Event I is injected while state machine is in state "s11." Again, the active state does not prescribe how to handle event I, so it is passed on to the next level of state hierarchy, that is, to state "s1." State "s1" has an internal transition triggered by I defined in its internal transition compartment; therefore "s1" handles the event (printout "s1-I;" in line (3)). And at this point the processing ends. No change of state ever occurs in the internal transition, even if such transition is inherited from higher levels of state hierarchy.