Nice Statechart Diagrams

2024 03 02 head

What are statecharts, also called hierarchical state machines?

Put simply, a statechart is a beefed-up state machine [1] [2]. The syntax is formally defined in the UML standard.

The beefing up solves a lot of the problems that state machines have, especially state explosion that happens as state machines grow.

We explain below what statecharts are and how they are useful.

Statecharts offer a surprising array of benefits:

  • It is easier to understand a statechart than many other forms of code.

  • The behavior is decoupled from the component in question. This makes it easier to make changes to the behavior. It also makes it easier to reason about the code. And the behavior can be tested independently of the component.

  • The process of building a statechart causes all the states to be explored. Studies have shown that statechart based code has lower bug counts than traditional code.

  • Statecharts lend themselves to dealing with exceptional situations that might otherwise be overlooked. As complexity grows, statecharts scale well.

  • A statechart is a great communicator. Non-developers can understand the statecharts, while the quality group can use statecharts as an exploratory tool.

It is worth noting that you are already coding state machines, except that they are hidden in the code.

Why Finite State Machines?

The actor model [5] is a powerful approach to create resilient and scalable distributed systems. Asynchronous communication between actors avoids global states and painful synchronization of primitives such as locks, semaphores or monitors.

Each actor defines his behavior with a finite state machine. The behavior is documented with a statechart.

Messages sent to an actor are the events triggering the firing of transitions.

Finite state machines and statecharts are part of the UML notation [1] [2].

Finite state machines should be deterministic. The set of all relevant events must be identified and documented. Consider encoding the events as an enumeration in your programming language.

At most, one transition can be fired when a specific event is processed. Use guard conditions to restrict which transition could be fired when multiple efferent transitions have the same triggering event.

A guard is a predicate used to decide if a transition can be fired. If your transition guards are using timeouts, you need a global time reference with the expected resolution.

A state in a finite state machine must have a duration. The actor implementing the finite state machine is in a state and waits for an input meaning a message, or a timeout event. Consider encoding the states as an enumeration in your programming language.

A transition has a trigger event, an optional guard and an optional action. The guard is a predicate to decide if the transition can be fired or not. A transition has semantically no duration. The action of a transition must be processed in a brief time.

Try to limit the parameter list of an action to the owner of the finite state machine and the triggering event.

Entry and exit actions are executed when the state is entered or exited.

State activities are active as long as the state is active. Try to avoid activities in control systems. An activity needs to be implemented as a separate thread and must be abruptly stopped when leaving the state.

We prefer to handle timing constraints as external events sent to the finite state machine instances. The source of timeout events is also the timekeeper of the system.

This approach relieves the designer of implementing a synchronous global time reference. Global synchronous time is tricky to implement in distributed systems with multiple processors. It is also arduous to test and sources of hard to find bugs.

How to Document Finite State Machines?

High-quality documentation tools are readily available to document your finite state machines. Most of the tools are open source and free to use.

  • plantUML provides support for all UML diagrams including statecharts. All features are supported. The graphical representation is dependent on the order of state declaration. You have to experiment if you want to render nice looking statecharts.

  • Mermaid provides nice finite state machines. Beware that entry and exit states are not supported. Transition guards and actions can be emulated but are supported by a specific notation.

  • Graphviz is the reference implementation for graph visualization. It is the Swiss army graph library. You can draw all types of graphs, but you are on your own.

  • state machine cat has the goal to draw beautiful statecharts.

  • Asciidoc Tables

Diagram Generators

Code-first approach is realized with the tangly open source FSM library.

The library executes finite state machines and generates documentation from the code. The code description of a finite state machine is the source of the diagrams.

The generator logic is straightforward.

  1. Generate the diagram preamble.

  2. Traverse recursively the hierarchical states.

    1. Generate the state preamble.

    2. Generate the optional shallow or deep history configuration.

    3. Generate the optional default state configuration.

    4. Generate entry and exit actions.

    5. Generate the optional final state configuration.

    6. Generate the state postamble.

  3. Generate all transitions

    1. Generate the source state name, the target state name, the event, optional guard, and action.

  4. Generate the diagram postamble.

The OS FSM defines generators for Java language and documentation targets.

The visual and tabular documentation is always synchronous with the source code.

This approach has one source of truth and minimizes documentation effort.

History

A standard to describe and execute UML Statecharts is SCXML [3]. The whole approach to document logic with XML notation was doomed. This approach is dying out. Do not use it.

Simple flat finite state machines can easily be implemented with two levels of nested switch cases. A flat state machine does not have any hierarchical, orthogonal or history states.
Ask yourself if an external library is worth the effort. I recommend defining the set of events and states as enumerations.

Libraries are mandatory if your state machines have hierarchical or history states. Entry and exit states are also easier to implement with a library.