Managing the Finite State Machine:
After circumventing the idiosyncrasies to a particular programming language and programming style the essence of “excellent” Software Engineering is simplifying the Finite State Machine (FSM). Before simplifying a FSM one must ponder what exactly is a Finite State Machine? Just as it sounds: it’s a machine that has finite states.
A computer is a FSM because at any point it can be in one of only a finite amount of states. Different states can range from interacting with the Arithmetic Logic Unit (ALU) or fetching a value from Memory or responding to an interrupt for I/O. Such a machine can also be handling multiple programs at once, each of these programs can be in different states: starting, finishing, paused.
Software, when properly designed, also is a finite state machine. For example when creating a server application a common requirement is for the server to respond to potentially many clients, share limited resources with the clients while preserving each connected client’s state. Preserving each client’s state is very important especially if the connected clients are operating without the awareness of other connected clients.
Managing a simple FSM is pretty straightforward and easy, the states to be accounted for don’t require exhaustive thought. However when the Finite State Machine becomes complex managing and preserving the states of the concerned parties becomes quite challenging. How does a Finite State Machine advance states while not disrupting the states of the connected parties? This problem often shows up in Multi-threaded programs in the form where Two or more processes/threads access a shared resource and both modify it for unrelated reasons.
Additionally multi-processing often uses locks to manage access to shared resources which can at times lead to “deadlocks“.
Some general take-aways when creating and working with complex Finite State Machines:
Avoid sharing state by isolating clients and resources (The clients should operate independently of each other).
Where possible if a client needs to modify a shared resource that other clients may not expect to change, opt to create a local copy.
Sometimes a shared resource maybe a hardware feature like Wi-Fi card in a device, if a wifi-device must change state, due to a request from a connected client, white in the middle of sending/receiving data from other clients design a system for “graceful” shut-down. You may save the state of the connected clients, so as to resume the interrupted state when resources are made available again.
When accessing shared resources that aren’t meant to be modified opt for using immutable structures. Opt for structures that are easily created, re-usable and destroyed.
As much as possible avoid “global actions”, actions that unnecessarily disrupt the state of other connected clients. For example: in a peer-to-peer app a requirement maybe to connect to a nearby device. However this is a global action that may disrupt the current active connection.
One must make sure the global action is necessary and in this case that the current connection is no longer needed or necessary.
Immutability and Localization guarantee that each connected client can operate as if it’s the only client interacting with the finite state-machine.
The less you have to organize the simpler and better.