How many of us have not had the experience of sitting in a classroom wondering idly: "Is this really going to matter out in the real world?" It's curious, and in no small amount humbling, to realize how many of those nuggets of knowledge really do matter. One cropped up recently for me: the Finite State Machine (FSM).
To set the context for how the FSM changed my life -- well, at least made developing software easier for me -- I need to digress somewhat. The company that I work for is in the process of releasing a new version of its product. The previous version was entirely Web-based, but for the new version we wanted to increase the level of interactivity. We settled upon Flash as the infrastructure for building some of our UI.
On the whole, our experience with Flash has been positive. It has been relatively easy to build UI components. The separation of mark-up from code means that the graphic designer can modify the visual aspects of the components without the involvement of a developer and with little risk to the code. The Flash event model, however, which seemed straightforward at first, ended up being the source of considerable grief for us.
Fundamentally, there were two issues with the event model that caused trouble. First, it seems like practically everything in Flash happens asynchronously. For example, when you first place a UI component into a visual container so that it will appear on the screen, you can't actually manipulate any properties of that component until you receive a completion event that indicates it is ready. Another example is server communications. All SOAP calls to retrieve data from the server are asynchronous. We found that all of this asynchrony was leading to code that was hard to understand and not robust.
For example, in many cases we have sequences of SOAP calls to the server that must be made in a particular order. Our first attempts at coding this turned out to be disastrous. We would make the first call to the server and hand it a callback routine to execute when that request was complete. In that callback routine, we'd make the second call to the server and hand it another callback routine to execute when that was complete. To make matters worse, sometimes the calls to the server were conditional -- that is, they were made in some circumstances and not in others. Our code quickly degenerated into a tangle of callback methods which were as hard to make sense out of as a bunch of spaghetti code littered with gotos.
The second issue which lead to our downfall was the way Flash deals with reentrancy for event handlers. My previous experience with this type of single-threaded programming was COM and its Single Threaded Apartment (STA). With the STA, when you are handling an incoming event, the COM runtime pretty much guarantees that your event handler will run to completion. This is an incredibly useful property because if a second event is dispatched to an object while it is still processing the first one, the second event handler is very likely to find the object in an inconsistent state.
Sadly, in Flash it is much easier for such event reentrancy to occur. In fact, our initial attempts at coding up our UI in Flash were riddled with situations in which we would get apparently random behaviour which, in the end, turned out to be caused by exactly this sort of reentrancy.
OK, so much for the pain and suffering part of this story. I realized that we needed a more structured way to deal with all of the asynchronous activity we were faced with. Lodged deep within my brain, resting undisturbed since some final exam many years ago, was the knowledge that finite state machines were one way to handle systems with asynchronous events. So we took a step back and started to sketch out how our UI could be envisioned as an FSM or collection of FSMs. Then we developed a standard implementation pattern that could be used to actually build those FSMs. Finally, we ripped out our existing callback and event handlers and put the FSMs in place.
I won't say much about the code itself, other than to note two principles which I think significantly help simplify the code and make it easier to understand. I'll also note that we're using the Model-View-Controller (MVC) pattern. This means that events/callbacks are always handled by the controller. The first principle is that the controller's event handlers and callback methods should do no work directly. When an event or callback arrives, all that happens is that the FSM is told that a particular event has occurred. This causes the FSM to change states and carry out certain actions. The second principle is that the FSM should not contain the detailed code necessary to carry out the actions associated with state transitions. In our case, the logic for these actions is actually part of the controller.
These two principles can be seen simply as an expression of the more general principle of "separation of concerns." The purpose of the FSM is strictly to govern state transitions. It should be as isolated as possible from the actual mechanics of the UI. The controller, on the other hand, should present an abstraction of the UI that hides details such as what controls are on the page and how those controls need to be manipulated to render a particular display.
Once we had our FSMs in place, development proceeded much more smoothly. One of the nicest aspects of FSMs is that they help identify coding errors quickly. The most common type of error when using an FSM is that an event arrives at the FSM and there is no corresponding transition associated with that event and the FSM's current state. In our FSMs, this opens an alert window reporting that an illegal event has been posted to the FSM. In the pre-FSM code, these sorts of unexpected events tended to cause the controller to enter an inconsistent state, but that inconsistency would generally not show up until sometime later, which made debugging especially challenging. The "stop on error" behaviour of the FSMs meant that we could quickly identify the source of the problem.
A second positive aspect of FSMs is that they are amenable to analysis. By this I mean that you can prove certain properties about them with relative ease. For example, we can prove that we are correctly handling the display of the busy cursor by demonstrating that all paths in the state machine pass through a state which turns the busy cursor on, and that subsequently those paths pass through a state which turns it off again. This is a fairly simple example, but with the unstructured collection of event handlers and callbacks that we had before, it was an area in which the code had bugs.
As we continue to develop the new UI for our product, we'll definitely be using FSMs wherever possible. And in the future, I believe that they are a technique that I would apply to almost all UI development, whether it was Flash, or some widget tool-kit combined with AJAX, or even a typical desktop application. UI development is always about dealing with events, and the FSM delivers a structured approach to handling those events.
Originally published in Queue vol. 5, no. 7—
see this item in the ACM Digital Library
Taylor Savage - Componentizing the Web
We may be on the cusp of a new revolution in web development.
Arie van Deursen - Beyond Page Objects: Testing Web Applications with State Objects
Use states to drive your tests
Rich Harris - Dismantling the Barriers to Entry
We have to choose to build a web that is accessible to everyone.
Conditional dependency resolution