This follows from the previous post, and is a reply to Serendipity Seraph.
In a previous incarnation of this idea I did indeed have some error handling, but I was never able to quite get it to feel like it belonged. It seemed wrongly conceived. My practice in these situations is to remove the feature or leave it out of new versions, which is what I did here, and wait to see if it wants to come back in.
After doing some more thinking about this, I came to the conclusion that error handling is actually a large part of what state machines are all about in the first place. You should enumerate all the different things that can happen (ie: conditions that can occur), have states for handling all of these. So, there is an argument for no special error handling; unexpected occurrences mean your state machine is incomplete.
(btw in the past I also had a handler for transitions between states, allowing checking things, and diverting transitions elsewhere, but that too turned out to be redundant, and the whole thing feels better for its omission.)
An example with a database connection failing: Database connections don’t tend to tell you when they die, rather you are going about your business happily, executing queries, then blam, out of the blue, a failure. In this state machine environment, this kind of synchronous error is best handled by a try/catch and raising a condition in the OnNewState handler, eg:
try
{
... code for doing things on entering states
... including a line that hits the database
connection.executesql(somesql); // or something like that, you get the drift
... more stuff, but we don't get this far
}
catch (DBException dbex)
{
DoSomethingWithException(dbex);
_stateMachine.RaiseCondition(DBConnectionFailure);
}
And of course you need to have transitions on DBConnectionFailure in your statemachine for every state where you don’t want to just ignore it. Note that what different parts of the machine need to do in case of DBConnectionFailure can be quite different.
A good contrast is where failures happen in an asynchronous manner. An example I recently worked on is where a modem drops out (loses Carrier Detect) (Yes, it’s appallingly old technology, but you get that at times). What happens in .net is that an event handler fires telling you that the pin state has changed for your serial connection. In the handler, you simply raise your condition (eg: _stateMachine.RaiseCondition(ConnectionLost)) and that’s all. The state machine will take it from there, assuming you’ve correctly set up your state machine to deal with ConnectionLost in all relevant cases.
I guess I could add some very simple support for error management, eg: a built in condition called UnexpectedErrorCondition, which the state machine would raise if the OnNewState handler ever throws an unhandled exception. You could have explicit transitions for this condition, but if you haven’t specified any, the machine would move to some specified error state (specified in the constructor just like Start and Stop are).
Another approach here would be setting up States hierarchically. In an hierarchical arrangement, when we look for transitions from state to state for a given transition, we would walk up the tree toward the root (ie: can’t find a transition for state x? Try x.parent, etc). This way “error handling” (ie: dealing with conditions which signal error conditions that can happen any time) can be dealt with at a high level grouping state, rather than having to be done explicitly at every state in the machine. I think that has promise, actually; currently, you do have to do a lot of cut and pasting to set up handling of error conditions explicitly in every state.