贴旧作:把状态机呈现给用户是拙劣的设计--从历史角度再论“状态机”

原创 2006年05月18日 21:24:00
    在IVR设计中,在早期采用状态机是无奈的选择,对应用程序的开发者而言(以下也称“用户”),状态机实际上是很难理解的概念,也是造成IVR设计复杂性的根源。不过这也给Intel CT ADE、蓝星际Koodoo语言一类的IVR开发工具带来了巨大的市场空间。
    提供给用户类似高级语言而非状态机的用户界面,给用户带来了很大的便利,但要实现这么一个带有强大编译功能的语音平台并不容易,其难度远远超过状态机实现,需要较强的开发实力,对平台厂商是个很大的挑战。


    为什么说状态机复杂的?

    这要从IVR语音开发的原始方式说起。作为语音板卡厂商基本上提供了两种类型的API,一种是以Dialogic为代表的事件驱动模式,另外一种是以国内厂商如东进、三汇的轮询模式;当然Dialogic接口较为丰富,除了事件驱动模式外也支持轮询等模式。
    大家也许没有意识到,传统方法上采用这两种模式的API开发应用程序其实都是状态机。
    所谓状态机(或有限状态机,即FSM),是指“用一组可能的状态来描述系统行为,系统在任何时刻只能处于其中的一个状态。也可以描述由输入值决定的状态转移。最后可以描述在某个状态下或状态转移期间可能发生的操作。”

    先来看看一段典型的Dialogic例子程序:
    (有关Dialogic的代码均摘自msidemo.c,版权属于Intel公司)
TABLE table[]=
  {/*current_state event       next_stat   function */
  { ST_WTRING,     DE_RINGS,   ST_OFFHOOK, setoffhk  },
  { ST_OFFHOOK,    DX_OFFHOOK, ST_PLAY,    play      },
  { ST_OFFHOOK,    DE_LCOFF,   ST_ONHOOK,  sethook   },
  { ST_PLAY,       TM_EOD,     ST_GETDIG,  get_digits},
  { ST_PLAY,       TM_MAXDTMF, ST_GETDIG,  get_digits},
  ...
  };
    这个结构描述了一个状态机,每一个状态都有状态名字如ST_WTRING,
事件如DE_RINGS,本状态完成后即将转移的下一个状态如ST_OFFHOOK,本状态对应的动作(函数)如setoffhk等等。作为一个最简单演示基本功能的程序其状态就有55个之多。
    驱动这些事件的核心是check_event()函数, 循环调用下列代码:
    if(dxinfo[channel].state == table[i].current_state
          && event == table[i].event){
       // 找到当前状态下对应的动作
       func_ptr = table[i].funcptr;
       dxinfo[channel].state = table[i].next_state;
       (*func_ptr)(channel);  // 执行这个动作
       ...
     }
     而执行的动作之中会根据情况,改变通道的状态。
     请注意,因为是多线路并发执行,所以几乎任何语音操作都是异步的,不允许任何的堵塞。

    好,我们再看看东进公司的一段例子程序:
    (有关东进的代码均摘自Dial/D.c,版权属于东进公司)
void WINAPI yzDoWork()
{
  ...
  for(int Line=0;Line<TotalLine;Line++){
     yzDrawState(Line);  //draw
     switch(Lines[Line].State){  //state transfer
        case CH_FREE:
           break;
        case CH_DIAL:
           if(CheckSendEnd(Line) == 1){
              StartSigCheck(Line);
              Lines[Line].State=CH_CHECKSIG;
           }
           break;
        case CH_CHECKSIG:
           tt = Sig_CheckDial(Line);
           if(tt == S_BUSY)
              Lines[Line].State = CH_BUSY;
           else if(tt == S_CONNECT)
              Lines[Line].State = CH_CONNECT;
           else if(tt == S_NOSIGNAL)
              Lines[Line].State= CH_NOSIGNAL;
           break;
        case CH_BUSY:
        case CH_NOSIGNAL:
           ...
     }
  }
}

    这也是一个典型的状态机,标识了很多状态,然后在每个状态下执行响应的操作,并且改变其状态--迁移到下一个状态。

    采样这种状态机的理由是,语音系统往往通道很多,每个通道看起来是并发操作,所以最简单是实现就是每个通道保存自己当前的状态,并进行迁移。
因为只有一个控制线程(或进程),所以每个状态下的操作不允许堵塞,如果某个线程执行一个耗时半分钟的操作,其它所有的线路将会同时引起停顿。
    我们也可以把状态看成是个时间片,你必须精心地划分好时间片,让操作足够地短。这类似早期的Windows3.x操作系统,是非抢占式的,所以把状态看成是命名的消息也是可以的。这类系统总有一个事件处理函数,去处理这些系统消息或用户消息(状态)。

    开发者为什么普遍觉得这样的程序难写?
    首先,如果应用复杂,状态是非常多的,经常达到数千个,开发者要仔细地划分这些状态是很大的工作量。
    其次,这些状态混在一起,没有层次,很难管理。因为这所有的状态地位都是平等的,是线性关系。这样的代码实际上也很难维护,造成了语音开发的门槛。
    第三,因为上述第二点的原因,业务操作的代码也只好和语音操作的代码混在一起,并且要强行对业务代码进行也进行状态划分,还需要小心避免业务的长操作。
    第四,造成应用开发人员被迫进行底层思维,比如一个放音操作,要人为地分解为1、开始放音,2、判断有没有放完,3、有没有被按键打断等等。
    第五,当线路较多时,容易造成性能的急剧下降。这主要是循环处理造成的。
    第六,流程的可读性变差,因为状态可以随意跳转而由于处理是线性结构很难看出流程的实际走向。
    第七,很难单步跟踪调试。
    第八,不容易以直观的方式实现循环,而很多业务实际上是需要限制次数的,如密码不对后的几次身份验证,重复播音次数,语音功能菜单最多操作次数等。

    目前市面上以新太为代表的语音平台产品,还是以状态机为核心,上述弊端基本上都存在,给客户带来的唯一方便就是避免了对底层板卡API编程,思维方式并没有变化。
    笔者在以前撰文指出过的新太脚本形同汇编,就是其死抱状态机教条带来的恶果,再怎么图形化也没有用。

    实际上,现代操作系统的发展和语音板卡API的发展给语音开发带来了全新的编程模式,这就是基于多线程的编程模式。这种编程思想的最基本出发点就是,把单一的通道限定在单一的线程之中执行,这样完全可以不必考虑时间片、消息、状态等额外的东西,语音操作也可以使用同步堵塞操作了,既符合程序员的思维,也符合业务流程的自然流向,并且可以彻底实现底层操作和业务操作的分离。
    以Koodoo语言为例子(Intel CT ADE类似),每个语音通道相当于一个虚拟机,虚拟机执行以Koodoo语言编制的业务流程脚本。而Koodoo语言具有现代高级语言的特性。这样彻底摆脱了状态机的桎梏,实现了语音开发的根本性的变化。

游戏状态机的设计与实现

状态机的模型是非常简单,但并不是每个人都能设计好的状态机。因为好的状态机不仅需要对程序的把握要比较到位,同时需要对整个业务的理解比较到位。好的状态机使程序变的更加简洁,易扩展,容易查找bug,还非常稳...
  • hackmind
  • hackmind
  • 2014年10月22日 08:40
  • 7654

java设计模式之State-状态机

State模式的定义: 不同的状态,不同的行为;或者说,每个状态有着相应的行为. 何时使用? State模式在实际使用中比较多,适合"状态的切换".因为我们经常会使用If elseif els...
  • tianmeng126
  • tianmeng126
  • 2012年12月29日 10:41
  • 4069

最佳实践----状态机对多步骤异步操作建模

在工作中,我们往往会遇到这样的问题,一个任务分为多个步骤,这几个步骤可能是连续的,也可能是可以跳转的,每个步骤都可能是异步的, 对于这样的问题,有没有一个通用的解决方案,或者是一个最佳实践呢,经过一...
  • owen_oragen
  • owen_oragen
  • 2015年11月20日 22:41
  • 723

设计一个有限状态机及其思路

# 前言我之前一直觉得状态机是一个比较难理解的概念。所以遇到相关的问题都会觉得自己理解得不是很透彻,上周一个同事在给我分析问题的时候,无意间就谈到了状态机的流程,在分析问题的时候,没想那么多,感觉很顺...
  • rainbowchou
  • rainbowchou
  • 2017年12月02日 12:55
  • 293

状态机嵌套的例子

该例是一个简化的EPROM的串行写入器。事实上,它是一个EPROM读写器设计中实现写功能的部分经删节得到的,去除了EPROM的启动、结束和EPROM控制字的写入等功能,只具备这样一个雏形。工作的步骤是...
  • politefish
  • politefish
  • 2009年10月07日 10:58
  • 2864

【FPGA】Verilog状态机设计

状态机是fpga设计中极其重要的一种技巧,掌握状态机的写法可以使fpga的开发事半功倍。 下面记录一下状态机的基本知识理论。 // 一段式状态机...
  • scottly1
  • scottly1
  • 2015年10月21日 10:20
  • 4529

设计模式之状态机模式

ZT From :http://www.jdon.com/designpatterns/designpattern_State.htm State模式的定义: 不同的状态,不同的行为;或者说,每个状...
  • mfc11
  • mfc11
  • 2013年11月24日 17:47
  • 20594

嵌入式系统中的状态机设计心得

在使用iTRON类OS的嵌入式系统中,除了驱动程序以外,大多数模块也就是中间件和应用程序是以任务(TASK)的形式设计的。而iTRON类OS大多采用C语言实现,于是用状态机的方式实现功能模块成为了主要...
  • xxxl
  • xxxl
  • 2015年04月13日 19:32
  • 1054

基于模型的PLC程序设计三 层次状态机

这次我们再在前面的例子上加深一步,写个含有父状态的状态机,同时用ST语言描述。控制描述有一个电机,按下启动按钮后低速启动,按下加速按钮后切换成高速,按下减速按钮后再切换成低速,如果按停止按钮,直接停止...
  • dronghtom
  • dronghtom
  • 2017年05月19日 07:27
  • 788

中断多任务+状态机 单片机软件结构设计(转)

mcu由于内部资源的限制,软件设计有其特殊性,程序一般没有复杂的算法以及数据结构,代码量也不大, 通常不会使用OS (Operating System),  因为对于一个只有 若干K ROM,...
  • u010980705
  • u010980705
  • 2016年09月30日 10:12
  • 754
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:贴旧作:把状态机呈现给用户是拙劣的设计--从历史角度再论“状态机”
举报原因:
原因补充:

(最多只允许输入30个字)