Finite State Machine Pattern - The One True Pattern?
Could all Code ever written be improved by applying the State Machine Pattern?
I was working on a project that was a mass of horrendous awful, buggy, broken spaghetti code. I copied Martin Fowler's example State Machine code from this blog and transformed the whole heap of crap into a series of statements. Liter开发者_StackOverflowally just a list of States, Events, Transitions and Commands.
I can't believe the transformation. The code is now clean, and works. Of course i was aware of State Machines before and have even implemented them but in the Martin Fowler example the separation of model/configuration is amazing.
This makes me think that almost everything i've ever done could have benefitted in some way from this approach. I want this functionality in every language i use. Maybe this should even be a language level feature.
Anyone think this is wrong? Or anyone have a similar experience with a different pattern?
Finite state machines (FSM's) and more specifically domain specific languages (DSL's) make it easier to match a problem to one specific solution domain, by describing the solution in a specialised language.
The limitations of the State Machine pattern is that it itself constitutes a programming language, but one for which you have to write your own execution, testing and debugging tools; and one which any maintainer has to learn. You have moved the complexity of your code into a complex FSM configuration. Occasionally, this is useful, but certainly not universally.
And since any von Neumann computer is itself a FSM, then certainly any program can be recast in this fashion.
Spaghetti code is never the right answer. Patterns are good at cleaning up spaghetti code, but just having a well thought out design can achieve the same thing.
With respect to the state-machine question: I personally find them to be very useful. For a public facing app I created, I refactored to use a state-chart (which I think is the same thing as the state-machine) and noticed the benefits you mentioned. It is a really useful abstraction, because it allows you to separate the concern of handling events out into a separate component. With this, bugs go away because the state-chart implicitly knows what a user can do where, and since the events are only handled in the right place, you can't really perform an invalid action. Also, using the state-chart allowed me to simplify the code because I could pull all the event handling code out of where it was and put it where it in one place where it was supposed to be -- in other words, other components weren't complicated by also having event handlers on them.
A picture is worth a thousand works -- Here is the design I came up with:
With this, anyone who looks at it knows exactly how the app behaves at a high level. Its hard to break this, because like I said, you can't get to an invalid state because the event handlers control exactly where you can go; if you can get to an invalid state, its an implementation bug that is easily fixed. Also, the code cleansing benefits are enormous. For example, with the state chart, whenever I enter any paused state, I can do things like stop the clock, put a mask on the board, etc. Its a few lines of code in one place -- because when i enter a substate of paused the chart goes thru the parent state first. without the statechart, I would have to do that code everywhere an event was handled.
I don't know if it needs to be a language level feature, but having a well implemented framework is nice.
I'll provide a counterpoint. Although I've definitely used them successfully from time to time, particularly with UI flows, those were one-or-two person projects. I personally think they could be an antipattern. I see this sort of code all the time, with things like network protocols and bot AI:
switch( myState ) {
case Starting:
...
if( such-and-such )
myState = WaitingForHandshake;
break;
case WaitingForHandshake:
...
if( such-and-such )
myState = SauteeingEggs;
break;
case SauteeingEggs:
...
etc
This code is usually prone to bugs and requires lots of logging and instrumentation. It's hard to reason about programs like this and prove to my satisfaction that it'll never end up in a bad state where it's executing the wrong code and checking for the wrong things. Add external callers changing myState to respond to events and more threads and it's a disaster.
Lately I've been thinking that most FSM's like this could be ... unwrapped, for lack of a better word ... and thought of as a history of events coupled with a logic unit that does queries on that history and then performs the appropriate response. It would take more memory, probably be slower, but I'd have a much better idea if the program was correct just by reading it. I wouldn't have to draw a state diagram or step through it in the debugger just to figure out what it's supposed to do in the first place. I've made some fairly simple AI bots that eschew the classic FSM pattern and do this instead that were quite succesful...but that's as far as I've taken this line of thinking so far.
Side note, I'm probably a Haskell programmer in another timeline.
精彩评论