很多人一听状态机,就觉得很复杂。
好像要画图。
要写框架。
要搞一堆设计模式。
其实在不少上位机项目里,先不用想那么重。
最重要的是把一件事想清楚:
当前状态下,收到某个事件,下一步应该变成什么状态。
如果这个关系没想清楚,代码里就会到处都是 if else。
if else 一开始很好写
项目刚开始,状态少,判断也少。
比如:
voidController::start(){if(m_state==State::Idle){m_state=State::Running;m_device->start();}}这很正常。
但后面要加暂停、继续、报警、复位。
代码就会变成这样:
if(m_state==State::Idle){...}elseif(m_state==State::Running){...}elseif(m_state==State::Paused){...}elseif(m_state==State::Alarm){...}每个按钮里来一份。
每个设备回调里再来一份。
时间久了,谁都不敢动。
因为你不知道这个判断到底服务哪个流程。
先把状态和事件写出来
状态机不一定一开始就写得很正式。
可以先把状态和事件列出来。
比如一个很简单的流程:
enumclassState{Idle,Running,Paused,Alarm};enumclassEvent{Start,Pause,Resume,Stop,AlarmRaised,Reset};这样写的好处是,项目里的人能先对齐语言。
什么叫运行中。
什么叫暂停。
什么情况下能复位。
这些话先说清楚,代码才不会越写越随意。
关键是限制不该发生的事
状态机最有用的地方,不是让代码看起来高级。
而是限制一些不该发生的操作。
比如空闲时可以启动。
运行中可以暂停或停止。
报警后只能复位。
暂停时可以继续,也可以停止。
这些规则可以先写得直白一点:
boolController::canStart()const{returnm_state==State::Idle;}boolController::canReset()const{returnm_state==State::Alarm;}按钮是否可点,也从这些函数来。
不要界面上自己判断一套,控制层又判断一套。
判断多了,迟早不一致。
状态变化要集中
上位机项目里,最怕状态到处改。
按钮里改一下。
设备回调里改一下。
定时器里改一下。
异常处理里再改一下。
后面出问题时,很难知道状态到底是谁改的。
可以先加一个统一入口:
voidController::setState(State state){if(m_state==state)return;appendLog(QString("状态变化:%1 -> %2").arg(stateText(m_state),stateText(state)));m_state=state;emitstateChanged(m_state);}这样至少能知道状态什么时候变了。
界面也可以只监听stateChanged来刷新。
状态不再靠各个按钮自己改。
不用一上来追求完美
很多项目不是缺一个复杂状态机框架。
而是连最基本的状态边界都没想清楚。
先做到这几件事,就已经比一堆 if else 稳很多:
- 状态用枚举写清楚
- 事件用名字说清楚
- 哪些状态允许哪些操作
- 状态变化统一入口
- 状态变化写日志
这些东西不花哨。
但现场排查问题时很有用。
状态能说清楚,流程才不容易乱。