控制结构(10): 指令序列(opcode)
// 上一篇:管道(pipeline)
// 下一篇:Continuation-passing_style(CPS)
发现问题
在一个正式项目的开发周期中,除了源代码版本控制外,还存在着项目的配置/编译/打包/发布等各种高频但非“核心”的脚本代码。职业程序员常常在写项目正式代码的时候,有着良好的习惯,包括编码规范/模块化/...等等。然而,当场景切换到配置、编译、打包、发布等脚本代码时,往往会写出蹩脚的代码。例如:全局变量满天飞、路径随便拼接、没有函数封装的裸奔代码、无任何注释和文档...
在这个过程中,“破窗效应”常常悄无声息在起作用。一个典型的现象是随着脚本的拷贝粘贴,项目根目录下会出现各种单一功能的脚本,例如:
- pub.sh
- pubGroup1.sh
- pubCI.sh
- pub.cmd
- pubCI.cmd
- clean.sh
- clean.cmd
- pubAndClean.cmd
- start.sh
- start.cmd
- stop.sh
- stop.cmd
- startAll.sh
- startCI.sh
- ....
小心!当这些“一键搞定”的“便利脚本”出现在项目的根目录下时,就应该引起你的警觉!软件项目在这些复制粘贴的脚本出现的时候,代码的规模也在大量膨胀;工程师发现和解决的BUG也在一波一波的来袭;工程师之间的骂声也在升级:“这个脚本怎么用?”“为什么你那边可以,我这边不可以”“不行,还是挂了”...
需求分析
那么,这个问题应该怎么解决呢?通过设计解决问题。不过,在设计之前要仔细分析下这些脚本的特征。我们看下pub.sh
会做的事情:
node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js stop -config config.json -group group2
node deploy/deploy.js stop -config config.json -group group3
node deploy/deploy.js pub -config config.json -group group1
node deploy/deploy.js pub -config config.json -group group2
node deploy/deploy.js pub -config config.json -group group3
node deploy/deploy.js start -config config.json -group group1
node deploy/deploy.js start -config config.json -group group2
node deploy/deploy.js start -config config.json -group group3
结论1:
发布脚本可能会针对多个组分别做
stop
,pub
,start
动作,并且这个pub
的名字被不恰当的用来做为整个脚本的名字。
接下来来看pubGroup1.sh
做的事情:
node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js pub -config config.json -group group1
node deploy/deploy.js start -config config.json -group group1
结论2:
发布脚本需要支持针对不同的分组操作。
然后,我们看下pubCI.sh
做的事情:
node deploy/makeconfig.js machinelist_ci.json
node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js stop -config config.json -group group2
...
结论3:
发布脚本会针对不同的集群从机器列表生成对应集群所需的配置
再分析下脚本的不同类型,有的是做pub的,有的是做stop的,有的是做start的。从而有
结论4:
发布脚本针对一个环境的指定分组有不同的操作需求。
程序设计
经过上述分析,我们基本搞清楚了这些看上去每个都是“一键”搞定的一个事情的脚本背后,有着一组复合的需求。在理清了这些需求之后,我们首先改变这些想法:“这是一次性脚本”;“这个脚本一键操作很方便”;“我复制一份改一下”。
在此之前,经过一番思考,并参考设计精良的命令行范本:git
,我们重新确定目标:
- 合并为单一脚本。
- 通过简洁的命令行参数满足不同的需求,命令行参数以操作做分组依据。
- 设计出简洁的配置指定不同的集群、不同的分组。
- 自由地忽略不需要的操作。
- 安全的预览即将执行的命令,避免误操作。
我们把这个单一脚本命名为dev.js
。文件的后缀名说明了我们将使用nodejs作为脚本的组织语言。
子系统配置设计
上述5个目标中,第3个是首先需要搞定的,如果配置的结构清晰,程序只是对配置所决定的结构的执行。配置的结构如下:
{
system = {
basic: {
push:[
{group:"zookeeper":, action:"stop"},
{group:"sleep":, action:"10"},
{group:"zookeeper":, action:"start"},
{group:"sleep":, action:"10"},
{group:"zookeeper":, action:"create"},
{group:"sleep":, action:"10"}
],
stop:[
{group:"zookeeper":, action:"stop"},
{group:"sleep":, action:"10"}
],
start:[
{group:"zookeeper":, action:"start"},
{group:"sleep":, action:"10"}
],
check:[
{group:"zookeeper":, action:"check"}
]
},
docker_image:{
push:[
{ group: "docker_image", action: "stop" },
{ group: "docker_image", action: "start" },
],
start:[
{ group: "docker_image", action: "start" },
],
stop:[
{ group: "docker_image", action: "stop" },
],
check:[
{ group: "docker_image", action: "check" },
]
},
servers:[
push:[
{ group: "server_1", action: "stop" },
{ group: "server_2", action: "stop" },
{ group: "server_3", action: "stop" },
{ group: "server_1", action: "pub" },
{ group: "server_2", action: "pub" },
{ group: "server_3", action: "pub" },
{ group: "server_1", action: "start" },
{ group: "server_2", action: "start" },
{ group: "server_3", action: "start" },
],
start:[
{ group: "server_1", action: "start" },
{ group: "server_2", action: "start" },
{ group: "server_3", action: "start" }
],
stop:[
{ group: "server_1", action: "stop" },
{ group: "server_2", action: "stop" },
{ group: "server_3", action: "stop" },
],
check:[
{ group: "server_1", action: "check" },
{ group: "server_2", action: "check" },
{ group: "server_3", action: "check" }
]
]
}
}
在这个配置结构里,有两层设计:
- 可以配置不同的
子系统
, 并且根据常见需求预定义内置的子系统,当某些情况下不满强需求时,可以增加子系统。 - 每个子系统下定义四种操作:
push
,start
,stop
,check
。
为什么要定义出一个叫做push
的操作呢?这是因为经过分析,下层的脚本提供了四个基本的原子操作:
- pub:将一个group拷贝到目标机器上。
- stop: 停止目标机器上的一个group
- start:开始目标机器上的一个group
- check:执行测试脚本检测一个group在目标机器上的基本运行情况。
那么,我们实际在部署的过程中,stop
,start
,check
三个操作都相对来说比较单一,不会有太多副作用。但是,pub
动作一般来说就是部署新版本的服务上去,此时:
- 它需要和其他的操作组合使用。
- 多个group之间会有相互依赖关系。
因此,我们将这样的一组复杂操作组合起来,统一用一个新的概念:push
来命名。对于子系统这样的层级,只提供push
,而不提供单独的pub操作。
设计了子系统配置之后,我们就可以满足不同的分组配置需求,这里的一个关键地方在于每个子系统的每个操作那边具体执行那些原子指令是可以通过配置进行编排
。
命令行参数设计
定义了上述子系统配置之后,接下来考虑命令行参数的设计,根据设计目标,学习git的命令行参数,我们以操作为中心组织options。
node dev.js push -system basic
node dev.js start -system basic
node dev.js stop -system basic
node dev.js check -system basic
上述命令已经可以这对子系统执行四个不同的操作。但是有时候我们希望只针对某个单一group做操作,因此,命令行将支持直接针对group的操作:
node dev.js push -group server1
node dev.js pub -group server1
node dev.js start -group server1
node dev.js stop -group server1
node dev.js check -group server1
在针对group的操作中,将group的pub
操作也暴露出来,提供给精细控制的情景使用。
进而,考虑第4个需求:在针对子系统的操作里应该能临时忽略某些子操作。因此,我们增加-i
选项:
node dev.js push -system basic -i start,stop
通过-i op1,op2,op3
这样的方式,我们可以忽略某些子操作。有时候,我们也希望忽略子系统的某些分组。那么可以增加一个新的忽略分组的命令行参数,但是,鉴于分组(group)的名字肯定不会和操作(op)的名字重合,我们可以直接让-i
选项里能忽略分组:
node dev.js push -system server -i stop,server2
到此为止,还缺了一个部件,那就是针对不同的集群切换配置的选项。这可以轻松增加:
node dev.js push -system server -env ci
增加了的env
参数变成一个必填参数,这样增加了可靠性:操作者必须知道自己针对哪个集群上部署,针对一些关键的集群,增加密码输入的要求。
预览的重要性
在上面的设计里,为了满足针对不同集群,忽略某些操作,子系统执行序列的配置。当用户敲入命令行,输入回车的瞬间,到底哪些命令会被执行?
为了解决这个问题,dev.js的执行部分设计两种不同的模式:
function exe(script,mode){
if(mode==='e'){
// execute the script...
else{
console.log(script);
}
}
而且,默认模式设置为预览
模式,所以默认情况下,当用户敲入回车时,只会看到即将执行的命令预览:
scripts:
node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js stop -config config.json -group group2
node deploy/deploy.js stop -config config.json -group group3
node deploy/deploy.js pub -config config.json -group group1
node deploy/deploy.js pub -config config.json -group group2
node deploy/deploy.js pub -config config.json -group group3
node deploy/deploy.js start -config config.json -group group1
node deploy/deploy.js start -config config.json -group group2
node deploy/deploy.js start -config config.json -group group3
please use `-e` to execute it.
只有当用户输入-e
选项时,才会真正执行,这样所有对自己的操作有疑惑的用户都可以放心的预览目标指令序列。
尾声
关于指令序列,有点像汇编的过程:
- 提供原子的指令。
- 提供组合原子指令的机制(例如分组)。
在程序设计的其他场景,例如测试用例的组织,异步链式逻辑的组织里,指令序列都有使用的机会。有N个原子的概念(核心概念),以及上层的一组对这些原子概念的组合(策略)。然后,人们常常只能看到上层的组合策略,因此会在组合策略的角度讨论A好还是B好。可是,因为这些组合策略不是原子的概念,所以无论说A好,还是说B好,都不会有结果,因为它们不是本质的。A和B策略,最终翻译后会变成底层的一组a,b,c操作序列,如果你能有一个探针,在底层监控a、b、c序列的执行,你把它们打印出来,你可以分析A、B上面出问题的时候,在底层序列里是哪里出问题了,例如可能是某个b正确执行所需要的条件不满足。
参考
[1] [tracing a packet journey using linux tracepoints](http://netsplit.com/tracing-on-linux
https://blog.yadutaf.fr/2017/07/28/tracing-a-packet-journey-using-linux-tracepoints-perf-ebpf/s)
[2] linux perf
控制结构(10): 指令序列(opcode)的更多相关文章
- 控制结构(10) 指令序列(opcode)
// 上一篇:管道(pipeline) 发现问题 在一个正式项目的开发周期中,除了源代码版本控制外,还存在着项目的配置/编译/打包/发布等各种高频但非"核心"的脚本代码.职业程序员 ...
- 循环-10. 求序列前N项和*
/* * Main.c * C10-循环-10. 求序列前N项和 * Created on: 2014年7月30日 * Author: Boomkeeper *******部分通过******* */ ...
- MariaDB 10.3 序列
在MariaDB .3版本中sequence是特殊的表,和表使用相同的namespace,因此表和序列的名字不能相同. MariaDB [wuhan]> select version(); +- ...
- wannafly 练习赛10 f 序列查询(莫队,分块预处理,链表存已有次数)
链接:https://www.nowcoder.net/acm/contest/58/F 时间限制:C/C++ 5秒,其他语言10秒 空间限制:C/C++ 262144K,其他语言524288K 64 ...
- 【AngularJS】—— 10 指令的复用
前面练习了如何自定义指令,这里练习一下指令在不同的控制器中如何复用. —— 来自<慕课网 指令3> 首先看一下一个小例子,通过自定义指令,捕获鼠标事件,并触发控制器中的方法. 单个控制器的 ...
- Velocity(10)——指令的转义
引用的转义使用"\",指令的转义也是使用"\".但是,指令的转义要比引用的转义复杂很多.例如: #if($foo) Go! #end $foo为true,输出G ...
- 【python cookbook】【数据结构与算法】10.从序列中移除重复项且保持元素间顺序不变
问题:从序列中移除重复的元素,但仍然保持剩下的元素顺序不变 解决方案: 1.如果序列中的值时可哈希(hashable)的,可以通过使用集合和生成器解决.
- msvc2010生成的指令序列有问题,可能跟pgo有关
正常序列 有问题序列 这段代码程序启动是执行,会导致崩溃 工程使用ltcg pgo,也就是说,第一次编译连接完成后,会跑一次profile,再执行连接器代码生成优化. 构建记录显示,ltcg已跑完,说 ...
- 循环-10. 求序列前N项和(15)
#include<iostream>#include<iomanip>using namespace std;int main(){ double i,n,t,a,b; ...
随机推荐
- [转]在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
本文转自:https://www.cnblogs.com/kongxianghai/p/5582661.html Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用Ja ...
- Java开发笔记(十三)利用关系运算符比较大小
前面在<Java开发笔记(九)赋值运算符及其演化>中提到,Java编程中的等号“=”表示赋值操作,并非数学上的等式涵义.Java通过等式符号“==”表示左右两边相等,对应数学的等号“=”: ...
- 【转】NotificationCopat.Builder全部设置
1.方法:setContentTitle(CharSequence title) 功能:设置通知栏标题. 例子:setContentTitle("测试标题"). 2.方法: ...
- react中的路由配置踩坑记
react 路由配置中,如果根路由(/)匹配一个组件,另一个路由(/list)在进行匹配的时候也会匹配到根路由(/),即在 /list 页面展示的时候 / 页面总是展示在上方. 此时如果想进行严格匹配 ...
- Puppeteer之大屏批量截图
事情的起因是客户三天两头的要求让我们给大屏截图,一定要最新的数据.所以我就自告奋勇研究了一下. puppeteer是一款headless浏览器,由谷歌官方维护.先看看puppeteer ...
- iOS----------关于Cornerstone的偏好设置
应用场景:有时候我们的项目会导入一些第三方.a文件,但是当我们在svn上check out下来的时候,会发现少了一些.a文件.于是乎项目就报错了. 针对这种情况,我们应该怎么办呢? 1.先打开Corn ...
- Tomcat 参数配置相关
Tomcat参数配置相关 by:授客 QQ:1033553122 目的: 对Tomcat配置的点滴学习总结,主要目的在于分析Tomcat与性能相关的一些参数设置,以便性能调优时选择最优配置 环境: ...
- asyncio异步IO——Streams详解
前言 本文翻译自python3.7官方文档--asyncio-stream,译者马鸣谦,邮箱 1612557569@qq.com.转载请注明出处. 数据流(Streams) 数据流(Streams)是 ...
- Ubuntukylin 14.04 系统语言改成中文[转]
1.在左侧点击"system setting" 2.按在图中方法设置 3.重启系统 参考地址:http://hi.baidu.com/thj2080/item/ae8e5dce ...
- oracle nvl2函数
nvl2(v1, v2, v3) 定义:如果v1为空,返回v3: 不为空,返回v2 nvl2要求v2,v3的类型一致,不一致会发生类型转换.问题:最终返回值类型是v2的类型还是v3的类型? 看题目:n ...