I’ve been neglecting an indisputable fact of the programming universe: everything is a Finite State Machine. Everything.

For the uninitiated, this won't be a primer on FSMs. That said, the basics are fairly mundane so don't be scared away.

Most applications of any significance are such complex state machines that modeling them explicitly would be a daunting task indeed. At the very least, it would require fantastic tools for creating and exploring highly nested, boundaried FSMs. There are notably few of those tools to my knowledge, possibly none.

There’s one category of programming, however, that lends itself incredibly well to definition by FSM: user interfaces. Every dynamic component on a web page is a state machine reacting to either client or server events. So why aren’t our UIs all modeled as state machines? I would venture it is mostly a tooling issue, but that’s an issue I’ll address in a future post.

Let’s look at an example: the humble button.

This pattern occurs everywhere, including your favorite JS framework, but I’m using jQuery for clarity. Ember / React developers like myself, however, will write substantially worse code with “Data down, actions up” and pat ourselves on the back for it.

$('.myButton').on('click', function(){
  let $btn = $(this);

  $btn.addClass('disabled');
  
  server.addTask(...)
        .then((result) => $btn.removeClass('disabled'));
});

The developer who wrote this code probably believed they were implementing the following simple state machine with two states and two transitions.

| STATE    | EVENT            | ACTIONS             | NEXT STATE |
|----------|------------------|---------------------|------------|
| Enabled  | click            | disableBtn, addTask | Disabled   |
| Disabled | responseReceived | enableBtn           | Enabled    |

I’m sure you’ve noticed, however, that this isn’t what’s implemented. Because they’ve only added the .disabled class and not the property, multiple clicks will trigger multiple click events and create duplicate tasks. There’s an easy fix for this, of course, but let’s step back and think about the big picture.

The code is lying to us. The code says $('.myButton').on('click', ...), or

“When someone clicks this button, I want to do some stuff.”

That’s not actually what we want. The reality is,

“When someone clicks this button, and I am in a state that permits me to submit this form, I want to do some stuff”.

This form is as simple as it gets, but the rules around state are often complex. These nuances get obscured in a combination of DOM elements and scattered conditionals, usually strewn across event handlers that mix business actions with DOM manipulation. Further, your domain logic – reject duplicate addTask requests – is actually dependent on the DOM! This is a clear violation of the Dependency Inversion Principle (the D in SOLID). Imagine the compounding effects of a more complex UI…

This is just as true with
click()
and other event handlers in your favorite web component framework.

Now compare this to the beautiful simplicity of the state machine. It succinctly expresses the developer’s intentions, and prevents many common errors.

Let’s pull the FSM thread a little further.

Usually, there are multiple elements on the page following this same pattern. Maybe when you click the button, you also disable some input fields, add an overlay, and display your custom dancing-cat loader gif. Duplication is often indicative of a missing abstraction. Let’s move one level up, and try modeling the form instead:

| STATE              | EVENT            | ACTIONS             | NEXT STATE |
|--------------------|------------------|---------------------|------------|
| AwaitingSubmission | submit           | addTask,disableForm | Submitting |
| Submitting         | responseReceived | enableForm          | Submitted  |
| Submitted          | -                | -                   | -          |

How to read rules (rows) in an FSM table:

Given I am in the
AwaitingSubmission
state, if I receive the
submit
event, perform the
addTask()
and
disableForm()
actions before transitioning to the
Submitting
state.

That’s great, but now we want to do a fancy animation to show the result of our form submission as we transition back to the task list. That doesn’t seem to fit here; let’s try abstracting up one level again:

| STATE      | EVENT       | ACTIONS                       | NEXT STATE |
|------------|-------------|-------------------------------|------------|
| TaskForm   | addTask     | ui.disableForm,server.addTask | AddingTask |
| AddingTask | taskAdded   | ui.fancyNewTaskAnimation      | ListTasks  |
| AddingTask | taskInvalid | ui.showErrors                 | TaskForm   |

Notice in this latest iteration, our state machine’s events live in the business domain. It’s also worth noting that the state machine does not care how the UI renders itself or how the server object creates a task, instead knowing only which actions to call for each state transition.

As a bonus for using domain events, we now don’t care where an event comes from. If we have a websocket pushing domain events to the client (perhaps a different user adds a task), we’re set up to handle it. Let’s add a rule:

| STATE      | EVENT       | ACTIONS                  | NEXT STATE |
|------------|-------------|--------------------------|------------|
| TaskList   | taskAdded   | ui.appendTask            | -          |

So if we’re viewing the task list, and a task is added, tell the renderer to append it. That was easy.

This post barely scratches the surface, but I think that’s enough for today. If you’re interested in how deep the rabbit hole goes, subscribe and send me an email with your experiences, suggestions, or just to say “Hey!”