TDD is great but sometimes I need an extra oomph into my design to help model a non-trivial problem. I find that modeling things as finite state machines help me to achieve this.
A finite state machine is a system where a set of inputs, called triggers, can cause the system to transition from one state of being to another state of being.
Why is this a useful way to model behavior?
- It constrains me to think in terms of a limited set of abstractions: triggers, states, transitions and side effects.
- It exposes edge cases I may not have thought of through pure TDD.
- It frees from me from thinking about things as sequential series of events, which other models such as Flowcharts can sometimes trap us into doing.
Before I model the state machine I ensure I understand the domain well enough to know the potential states and triggers. A state can be one value (like an enum) or a combination of values. A trigger can be any event, user action, or time passing. Here are the steps I usually take:
- I start by making a grid out of these states and triggers, then filling in the transitions (destination State) and any side effects.
- Then I diagram the state machine visually using Draw.io or similar tool so that my pair partner or other devs can easily understand the solution being developed. I try not to underestimate the cost of knowledge transfer on a team.
- Finally, I test-drive out the functionality in the code itself. Even though I modeled the scenarios ahead of time, this let’s me flesh out those nitty-gritty details, like what the triggers look like, the surface area of the state machine, how it needs to fit into the codebase, etc.
Let’s take an example. I just had a beautiful baby girl, so let’s use a newborn baby as a state machine. A book I am reading, The New Father, says that there are 6 states to a newborn baby: Quiet Alert, Active Alert, Crying, Drowsiness, Quiet Sleep, Active Sleep.
Transitions are a bit trickier and require us to infer a bit, as it will be with most problems we are solving for a customer. Crying is a (well known) state and all parents likely know that babies cry due to a few things: they are hungry, they have a dirty diaper, and they need comfort. The latter covers range of comfort needs, like temperature change and parental affection but I lumped them together for simplicity. From the book we know that Quiet Sleep and Active Sleep switch every 30 minutes so time passing is another trigger. Other triggers may include sudden light or noise and physical contact. Also note that transitions can be both conditional (only happens some of the time based on trigger context) and unconditional (will always happen when the trigger occurs).
So now we have a grid we can setup:
|Quiet Alert||Active Alert||Crying||Drowsiness||Quiet Sleep||Active Sleep|
|Lack of comfort|
|30 min interval|
In the next post in the series we will fill in the transitions and diagram the machine.