// 上一篇:卫语句(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. 用状态机STATE MACHINE实现有选择的文件转换

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

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

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

  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. 桥接模式二(Bridge)

    昨天写到了桥接模式的代码实现,今天我们就继续来讲完桥接模式.      有认真看的会发现,昨天的代码实现只是两个维度的变化:哪么有人可能就会问了哪如果我要加多一个维度呢?我要具体到企业的哪个部门,哪我 ...

  2. Python 使用一等函数实现设计模式

    案例分析:重构"策略"模式 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 如果合理利用作为一等对 ...

  3. ObjC如何通过runtime修改Ivar的内存管理方式

    ObjC如何通过runtime修改Ivar的内存管理方式 为什么要这么做? 在iOS 9之前,UITableView(或者更确切的说是 UIScrollView)有一个众所周知的问题: propert ...

  4. .Net Framework下对Dapper二次封装迁移到.Net Core2.0遇到的问题以及对Dapper的封装介绍

    今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和大家分享一下.下面我会还原迁移的每一个过程 ...

  5. 【Zookeeper】角色、顺序号、读写机制

    角色 leader 负责进行投票的发起和决议,更新系统状态 learner 包括follower和observer follower用于接受客户端请求并向客户端返回结果,在选举过程中参与投票 obse ...

  6. vue调试工具vue-devtools安装及使用

    本文主要介绍 vue的调试工具 vue-devtools 的安装和使用 工欲善其事, 必先利其器, 快快一起来用vue-devtools来调试开发你的vue项目吧 安装:  1.到github下载: ...

  7. java web方面的面试问题,Spring MVC方面的面试问题,摘自java web轻量级开发面试教程

    本文摘自java web轻量级开发面试教程: https://baike.baidu.com/item/Java%20Web%E8%BD%BB%E9%87%8F%E7%BA%A7%E5%BC%80%E ...

  8. MyBatis记录

    记录一下MyBatis的几个模块大纲,除去缓存以及集合映射两个部分 Mybatis架构 1. mybatis配置 SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了myb ...

  9. C++语法细节笔记

    1.数据类型转换 当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数. 把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数后的模. ...

  10. DispatcherTimer和Timer(计时器)

    System.Windows.Threading.DispatcherTimer dTime;        System.Timers.Timer timer;        public Main ...