// 上一篇:卫语句(guard clause)

// 下一篇:局部化(localization)


基于语言提供的基本控制结构,更好地组织和表达程序,需要良好的控制结构。

前情回顾

上次分析了guard 控制结构,有人说:“可是阅读很多开源代码也是if/else层层嵌套的”。是的,guard控制结构只是一种风格,语言并没有规定你一定要这么写,也没必要。两种风格都能写出逻辑清晰的代码,但是还是有细微差别,嵌套模式下,要写出清晰的代码需要更好的做好函数的分层(见第1节),例如,在分支里只做函数的转发调用,那么,代码也是清晰可读的。

function doSomething(){
if(condition1){
call1();
}else{
if(condition2){
call2();
}else{
call3();
}
}
}

我们来看一个新的结构。

典型代码:


let opening = false;
let opened = false;
let closing = false;
let closed = false;
let step1 = false; function open(onComplete){
if(closing||closed){
return onComplete(RESULT.CLOSED)
} if(opening){
return onComplete(RESULT.PENDING);
} if(opened){
return onComplete(RESULT.SUCCESS);
} opening = true;
doOpen(function(err){
onComplete(err);
opening = false;
opened = true;
});
} function close(onComplete){
if(!opened){
return onComplete(RESULT.INVALID_OP);
} if(closing||closed){
return onComplete(RESULT.PENDING);
} closing = true;
doClose(function(err){
onComplete(err);
closing = false;
closed = true;
})
} function start(onComplete){
open((err)=>{
if(err){
return onComplete(err);
}
step1((err)=>{
if(err){
return onComplete(err);
}
step2((err)=>{
onComplete(err);
close((err)=>{
log(`close, ret:${err}`);
})
})
})
})
} function step1(onComplete){
if(!opened){
return onComplete(RESULT.NOT_OPEN);
} doStep1(function(err){
step1 = true;
onComplete(err);
});
} function step2(onComplete){
if(!opened){
return onComplete(RESULT.NOT_OPEN);
} if(!step1){
return onComplete(RESULT.INVALID_STEP);
} doStep2(function(err){
onComplete(err);
});
}

结构分析

这段代码使用了多个bool变量来做状态标记,为了安全有序的做好每个异步动作,需要在适当的地方对每个bool标志变量做判断。如果这个流程继续复杂起来,bool变量之间的状态组合会变的更加繁杂,也很容易一不小心就漏掉状态的组合判断,导致难以定位的BUG。此时,我们可以采用一种重要的控制结构。

...

是的,状态机(state machine)结构。我们先来看看使用状态机控制结构改写后的代码:


/**所有的状态*/
const STATE = {
INIT: 0,
OPENING: 1,
OPENED: 2,
STEP1: 3,
STEP2: 4,
CLOSING: 5,
CLOSED: 6,
ERROR: 7
} /**每个状态都只允许从指定状态转移过来*/
const stateTransfer = {
STATE.INIT: [],
STATE.OPENING: [STATE.INIT],
STATE.OPENED: [STATE.OPENING],
STATE.STEP1: [STATE.OPENED],
STATE.STEP2: [STATE.STEP1],
STATE.CLOSING: [STATE.INIT, STATE.OPENING, STATE.OPENED, STATE.STEP1, STATE.STEP2],
STATE.CLOSED: [STATE.CLOSING],
STATE.ERROR: [STATE.INIT, STATE.OPENING, STATE.OPENED, STATE.STEP1, STATE.STEP2],
} let state = STATE.INIT;
let stateChangeEvent = null; function moveTo(err, newState){ let nextState = err ? STATE.ERROR : newState; if(stateTransfer[nextState].indexOf(state)===-1){
return RESULT.INVALID_STATE_TRANSFER;
} let oldState = state;
state = nextState;
stateChangeEvent(oldState, state, err); do(); return RESULT.SUCCESS;
} function do(){
switch(state){
case STATE.INIT:
moveTo(0, STATE.OPENING);
break;
case STATE.OPENING:
doOpen((err)=>{
moveTo(err, STATE.OPENED);
});
break;
case STATE.OPENED:
doStep1((err)=>{
moveTo(err, STATE.STEP1);
});
break;
case STATE.STEP1:
doStep2((err)=>{
moveTo(err, STATE.STEP2);
});
break;
case STATE.STEP2:
moveTo(0, STATE.CLOSING);
break;
case STATE.CLOSING:
doClose((err)=>{
moveTo(err, STATE.CLOSED);
});
break;
case STATE.CLOSED:
break;
default:
break;
}
} function start(onStateChange){
stateChangeEvent = onStateChange;
do();
}

语义分析

看上去好像多了很多代码。然而在复杂的异步处理流程中,状态机能清晰地表达逻辑,保证逻辑代码的时序,并在形式推理上发现和消灭漏洞,阅读do里面的逻辑,状态之间的转换直接、明显。编程语言提供了基本的状态标志位:bool变量。这便于让程序员在编程中随意地使用bool变量标记各种状态,但0/1状态标示在稍微复杂一点的状态变化中,就会显得不好用:

  • 出现组合爆炸,你需要对多个bool变量做组合判断,这很容易漏掉某个状态组合。
  • 设计上的失误,导致bool标志位之间不是正交的,因而任一时刻,程序的状态不能确保唯一,这会导致程序出现未知的时序问题。

事实上,任何程序在运行中本身就是一个状态机:

  • 当前在第i行执行
  • 根据条件,进入第j行执行

这里的状态就是:

  • pc, 也就是program counter, 程序计数器
  • condition,改变pc的条件

如果想更清楚地理解这点,可以观看Lamport制作的视频,要看到第2集:

http://lamport.azurewebsites.net/video/intro.html

控制结构(3): 状态机(state machine)的更多相关文章

  1. 控制结构(3) 状态机(state machine)

    // 上一篇:卫语句(guard clause) // 下一篇:局部化(localization) 基于语言提供的基本控制结构,更好地组织和表达程序,需要良好的控制结构. 前情回顾 上次分析了guar ...

  2. 用状态机STATE MACHINE实现有选择的文件转换

    用书上的例子实现在解析HTML文本时,对"<>"中的符号不进行字符转换. import sys import string from optparse import O ...

  3. Android4.0中蓝牙适配器state machine(状态机)的分析

    今天晓东和大家来一起看一下Android4.0中蓝牙适配器(Bluetooth Adapter)的状态机变化的过程.首先,我们需要了解一下,蓝牙适配器究竟有哪些状态,从代码可以清晰地看到(framew ...

  4. 【UML】-NO.43.EBook.5.UML.1.003-【UML 大战需求分析】- 状态机图(State Machine Diagram)

    1.0.0 Summary Tittle:[UML]-NO.43.EBook.1.UML.1.003-[UML 大战需求分析]- 状态机图(State Machine Diagram) Style:D ...

  5. 【翻译】What is State Machine Diagram(什么是状态机图)?

    [翻译]What is State Machine Diagram(什么是状态机图)? 写在前面 在上一篇学习类图的时候将这个网站上的类图的一篇文章翻译了出来,感觉受益良多,今天来学习UML状态机图, ...

  6. Java Secret: Using an enum to build a State machine(Java秘术:用枚举构建一个状态机)

    近期在读Hadoop#Yarn部分的源代码.读到状态机那一部分的时候,感到enmu的使用方法实在是太灵活了,在给并发编程网翻译一篇文章的时候,正好碰到一篇这种文章.就赶紧翻译下来,涨涨姿势. 原文链接 ...

  7. Raft算法系列教程2:状态机复制 (State Machine Replication)

    分区容错如何保证? 在分布式系统设计中,需要遵循CAP理论,如果我们要让一个服务具有容错能力,那么最常用最直接的办法就是让一个服务的多个副本同时运行在不同的节点上.但是,当一个服务的多个副本都在运行的 ...

  8. State Machine.(状态机)

    What is a State Machine? Any device that changes its state from one to another due to some actions a ...

  9. Finite State Machine 是什么?

    状态机(Finite State Machine):状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动       作.完成特定操作的控制中心. 类 ...

随机推荐

  1. MySQL 笔记整理(2) --日志系统,一条SQL查询语句如何执行

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 2) --日志系统,一条SQL查询语句如何执行 MySQL可以恢复到半个月内任意一秒的状态,它的实现和日志系统有关.上一篇中记录了一 ...

  2. Error - The debugger's worker process (msvsmon.exe) unexpectedly exited.

    Error - The debugger's worker process (msvsmon.exe) unexpectedly exited. 解决方法:Tools->Options-> ...

  3. Mongo基础 索引的使用

    MongoDB中的索引和其他数据库索引类似,也是使用B-Tree结构.mongodb的索引是在collection级别上的,并且支持在任何列或者集合内的文档的子列中创建索引. 所有的MongoDB集合 ...

  4. input type date 解决移动端显示placeholder

    在最近的一个项目中使用到了html5的一个新标签属性,type="date"时,发现placeholder属性失效无法使用. 如果是这样的效果,那么客户体验是可想而知的差了. 最后 ...

  5. 安装PackageControl

    安装PackageControl 1,到PackageControl官网,查找到相应sublime text的版本安装信息, sublime text 3: import urllib.request ...

  6. angular路由为空重定向到指定路由

    { path: '', redirectTo: 'home', pathMatch: 'full' }

  7. iOS-UIView指定圆角设置

    圆角设置可以指定左上.左下.右上.右下角:单个指定或多个指定. ///设置圆角[左上.右上角] - (void)setCircular{ UIBezierPath *maskPath = [UIBez ...

  8. Java新知识系列 七

    抽象类和接口的区别和特点 java的JDK中包含的五个工具 编译型语言和解释型语言 Java和C++的区别` 常见的ASCII的值 Forward和Redirect之间的对比 Web Service ...

  9. Network Policy - 每天5分钟玩转 Docker 容器技术(171)

    Network Policy 是 Kubernetes 的一种资源.Network Policy 通过 Label 选择 Pod,并指定其他 Pod 或外界如何与这些 Pod 通信. 默认情况下,所有 ...

  10. Tomcat开启SSL协议支持

    一.生成keyStore 要使用ssl connector,必须先创建一个keystore.他包含了服务器中被客户端用于验证服务器的数字证书.一旦客户端接受了这个证书,客户端就可以使用public k ...