重构背景

最近在开发这样一个管理系统:将公司多台服务器上的查询SQL相关信息(用户,查询语句,查询类型,起始时间,结束时间等)收集起来,持久化到数据库,并在页面上统一展示。其中最基本的也是最重要的部门就是从每个服务器上获取SQL信息,然后进行相应的处理。最初的程序伪代码如下所示:

if (SQL完成) {//存入数据库
} else {while (true) {//获取最新SQL查询状态if (SQL完成) {//存入数据库break;}}
}

只需要从服务器获取SQL最新的查询信息,然后判断该SQL是否执行完成,执行完成之后存入数据库中。处理逻辑也非常简单,一个if-else判断就能搞定。但是在往后开发的时候,需求又进行了更新,还有一些其他的情况需要进行处理,此时程序的伪代码如下所示:

if (SQL完成) {//存入数据库
} else {while (true) {//获取最新SQL查询状态if (SQL完成) {//存入数据库break;} else if (SQL正在执行) {if (执行时间 > 执行时间阈值) {//取消SQL执行//存入数据库}} else if (SQL等待) {if (等待时间 > 等待时间阈值) {//取消SQL等待//存入数据库}}}
}

在新的处理逻辑中加入了两个新的判断分支:分别是执行超时和等待状态。对于这两种情况我在这里就不解释其含义了。对于执行超时,用户可以设置一个执行时间的阈值。当SQL执行时间超过该阈值时,就取消该SQL的执行,以释放资源;而对于等待状态,当SQL已经在等待超时状态时,如果等待时间超过该阈值,则取消该SQL的等待,直接进行后续处理。
对于每个服务器上的每一条SQL查询都会进行这样一个循环判断,从伪代码就不难看出,逻辑处理比较复杂,而且代码看起来不优雅。此时,我们可以明显发现,其实上面的各个if-else分支判断,本质上就是一个SQL的各个状态之间的转移。因此我们很自然的就想到了使用状态机编程的方式,对这部分代码进行重构。

开始重构

与一般的编程方法不同,状态机编程主要就是将程序划分为各个不同的状态,并且定义了每个状态对应的行为以及相关的状态转换关系。说起来可能比较抽象,下面就结合上面所说的例子,来具体了解下什么是状态机编程。
首先,对于上面的伪代码我们可以看到,SQL在执行的过程中一共有四种状态:完成,运行中,执行超时和等待超时。这几种状态之间有相互转换的可能,例如运行中的SQL可能完成,也可能执行超时或者等待超时。因此,我们根据SQL的这些执行状态定了如下的枚举:

public enum QueryStatus {START, RUNNING, WAITING, TIMEOUT, FINISH, STOP
}

下面来对上面的各个执行状态进行解释:
- START:表示刚开始时,从服务器获取SQL的查询信息;
- RUNNING:表示SQL正在执行过程中;
- WAITING:表示此时SQL已经处于等待的状态;
- TIMEOUT:表示SQL执行超时或者等待超时;
- SUCCESS:表示SQL执行完成,此时可以持久化到数据库中;
- STOP:结束状态,不做具体操作。
解释完了各个状态的含义之后,我们来看看各个状态之间的状态转换图:

关于每个状态之间的状态转移情况就再赘述了,大家只要知道这些状态之间有这样的转换关系即可。
下面为了使用状态机编程,还定义了另外一个类用于保存SQL的状态及查询信息,如下所示:

public class QueryEntity {private QueryStatus status;private QueryInfo info;//省略余下的get和set方法
}

其中,QueryInfo就是用于保存SQL的查询信息。然后,我们就可以根据上面的状态转换图进行编码了,这里只展示关键部分的代码,对于相关的上下文则略去:

public void handle(QueryInfo info) {QueryEntity entity = new QueryEntity(QueryStatus.START, info);while (entity.getStatus() != QueryStatus.STOP) {switch(entity.getStatus()) {case START:start(entity);break;case RUNNING:running(entity);break;case WAITING:waiting(entity);break;case TIMEOUT:timeout(entity);break;case FINISH:finish(entity);break;default:logger.error("Unknown query status: ");break;}}
}private void start(QueryEntity entity) {//初始的获取信息操作
}private void running(QueryEntity entity) {//运行状态的处理逻辑,如果执行超时则转换为TIMEOUT;否则为FINISH
}private void waiting(QueryEntity entity) {//等待状态的处理逻辑,如果等待超时则转换为TIMEOUT;否则为FINISH
}private void timeout(QueryEntity entity) {//取消SQL的执行或者等待,然后转换为FINISH
}private void finish(QueryEntity entity) {//SQL处理完毕,执行持久化操作//只有在这里,状态才会转换为STOP,并退出状态机
}

至此,本次重构就已经完成。我们可以看到,使用状态机编程方法进行重构之后,代码逻辑变得更加清晰和易懂,而且状态之间的转换也不容易出错。代码也更加优雅。然后就是相关的代码review和测试过程了。这就不是本文的重点了。

延伸学习–状态模式

状态机最早并不是来源于软件开发,但是现在的应用非常广泛,例如音乐播放器之间的各个状态变换也是使用了状态机编程。在设计模式中有一种状态模式,就是使用了这种思想。
定义:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这里一共包含了两部分的含义:

  1. 状态模式将状态封装成了独立的类,并通过代表当前状态的对象来进行相应的动作;
  2. 从用户的角度来看,如果对象能够完全改变其行为,那么该对象是从别的类实例化来的。然而,状态模式是使用组合,通过引用不同的状态对象来造成类改变的假象。

下面来看看状态模式的类图:

从类图可以看出,我们在上面所使用的状态机编程与这里有一些不同。状态模式首先抽象了一个状态类的接口,然后为每个状态实现了一个具体的状态类。下面就根据状态模式的定义将上面的例子改为状态模式的编码。
首先,定义一个抽象状态接口,并定义一些通用的方法,然后对每一种状态都实现一个具体的状态类。这里只写了START和STOP对应的状态类作为演示:

public interface State {public void handler(QueryInfo queryInfo);
}public class StartState implements State {QueryContext context;public StartState(QueryContext context) {this.context = context;}public void handler(QueryInfo queryInfo) {//和上面的start()方式执行相同的处理逻辑//可以通过context.setState()来改变状态}
}public class StopState implements State {QueryContext context;public StopState(QueryContext context) {this.context = context;}public void handler(QueryInfo queryInfo) {//退出本次处理context.setStop = true;}
}

下面就是查询对应的Context类,通过调用Context的handler方法可以将动作委托给具体的状态来执行:

public class QueryContext {State startState;State runningState;State waitingState;State timeoutState;State finishState;State stopState;State state = startState;boolean isStop;public QueryContext() {startState = new StartState(this);runningState = new RunningState(this);waitingState = new WaitingState(this);timeoutState = new TimeoutState(this);finishState = new FinishState(this);stopState = new StopState(this);isStop = false;}public void handler(QueryInfo queryInfo) {state.handler(queryInfo);}public void setState(State state) {this.state = state;}public State setStop(boolean isStop) {this.isStop = isStop;}public State isStop() {return isStop;}
}

下面是一个简单的测试类,用于验证我们的这个状态处理机:

public class QueryContextTest {public static void main(String[] args) {QueryContext context = new QueryContext();QueryInfo queryInfo = GetTestQueryInfo();while (!context.isStop()) {context.handler(queryInfo);}}
}

可以看到,使用状态模式对上面的例子进行修改之后,代码量反而增加了不少,而且逻辑看上去也复杂了。所以说,上面的情况并不适合于使用状态模式进行处理。状态模式比较适合对于每种状态,都有好几个操作进行,而上面的例子中,就只有一个handler方法。因此,只需要定一个状态枚举即可。在实际开发过程中,我们也应该灵活处理问题,而不是生搬硬套。

记一次代码重构--状态机编程相关推荐

  1. 代码重构之路 --我的2022年总结

    2022年是我正式参加工作的第10个年头,也是我在CSDN上写博客的第11个年头.在这10余年的时间里,虽然在工作上遇到了各种情况,但我一直坚持输出.坚持分享,一共在CSDN上发表了530多篇原创博文 ...

  2. java编程代码大全_掌握Java编程技巧,代码重构

    代码重构在不改变软件系统外部行为的前提下,改善它的内部结构,通过调整程序代码改善软件的质量.性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性. 代码重构目标 持续纠偏和改进软件设计 随 ...

  3. 嵌入式状态机编程简介

    不知道大家有没有这样一种感觉,就是感觉自己玩单片机还可以,各个功能模块也都会驱动,但是如果让你完整地写一套代码,却无逻辑与框架可言,上来就是开始写!东抄抄西抄抄,说明编程还处于比较低的水平.那么,如何 ...

  4. 系统重构的原则代码重构的原则

    作者:[美]马丁•福勒(Martin Fowler) 译者:熊节, 林从羽 前一章所举的例子应该已经让你对重构有了一个良好的感觉.现在,我们应该回头看看重构的一些大原则. ##2.1 何谓重构 一线的 ...

  5. 把三千行代码重构为15行

    2019独角兽企业重金招聘Python工程师标准>>> 如果你认为这是一个标题党,那么我真诚的恳请你耐心的把文章的第一部分读完,然后再下结论.如果你认为能够戳中您的G点,那么请随手点 ...

  6. ZVM – 记VMP保护代码还原工程

    目录: 1. 前言. 2. 渊源. 3. 还原大纲 4. 开始还原 -- X86乱序整理. 5. 开始还原 –X86膨胀压缩. 6. 开始还原 –前X86(压缩后)转为Vm指令 7. 还原核心 – V ...

  7. bilibili深入理解计算机系统笔记(2):第一次代码重构,汇编模拟器,递归,指令周期实现。

    文章目录 深入理解计算机系统笔记(2) 第一次代码重构 可变参数输出print函数 bitmap学习 P10 有限自动机 指令周期 递归求和函数c语言和汇编语言 回调函数的实现 call和ret指令的 ...

  8. python之穿越火线游戏代码_Python实现拼字游戏与代码重构

    有位文豪说得好:"看一个作家的水平,不是看他发表了多少文字,而要看他的废纸篓里扔掉了多少." 我觉得同样的理论适用于编程.好的程序员,他们删掉的代码,比留下来的还要多很多.如果你看 ...

  9. 工程师必知的代码重构指南

    作者 | CATE LAWRENCE 译者 | 冬雨 策划 | 蔡芳芳 本指南将带你了解进行代码重构的好处.可能遇到的挑战.可以采用的工具和最佳实践,以及重构和技术债务之间的区别. 我们都在寻找清理代 ...

  10. php代码重构,Shell在代码重构中的应用了解下

    代码重构(Code refactoring)有时是很枯燥的,字符串替换之类的操作不仅乏味,而且还容易出错,好在有一些工具可用,以PHP为例,如:Rephactor,Scisr等等,不过现成的工具往往意 ...

最新文章

  1. Fastp对fastq质控
  2. (转)JS window对象的top、parent、opener含义
  3. bgi::detail::comparable_margin用法的测试程序
  4. 关于如何将轮播图在移动端和pc端自适应的操作
  5. 收集到的非常好的第三方控件
  6. 球体动画Android,Android自定义View实现简单炫酷的球体进度球实例代码
  7. Intel处理器家族和分类
  8. 新手学java 学哪方面_初学者学Java应从哪些方面学习?
  9. Installshield2010实现web部署和数据库安装示例
  10. python 安卓库_Android高级面试题资料(持续更新)
  11. 《运算放大器权威指南》读书笔记(二)
  12. 安装西门子博图一直重启_博图软件常见错误解决方法
  13. Linux实战教学笔记18:linux三剑客之awk精讲
  14. 在Excel表格里面一点就出现“√”符号选项的方法
  15. Maven详解【Idea搭建Maven项目、Maven常用指令、Maven的传递性和依赖性、排除依赖】
  16. E波段通信系统参考文献E-band info(整理)
  17. linux查看进程线程的方法
  18. 仿ofo单车做一个轮播效果
  19. WinPE_USB启动盘制作
  20. 微信小程序的页面导航

热门文章

  1. 竞品分析文档撰写总结
  2. winform打开PDF,显示在窗口
  3. sonarqube + nexus 分析项目组成员代码状况,并生成报表
  4. Ubuntu下如何截屏
  5. linux查看系统内存
  6. SDL Trados2017及SDL MultiTerm安装
  7. 稳压二极管限流电阻怎么选择
  8. 以比例阀控制为例详解PI控制参数设计(附参数设计代码)
  9. 横向合计代码 锐浪报表_[原创]锐浪报表动态加入列和最后加入合计列+进度条显示...
  10. 网络地址与直接广播地址有关计算