// 上一篇:管道(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,我们重新确定目标:

  1. 合并为单一脚本。
  2. 通过简洁的命令行参数满足不同的需求,命令行参数以操作做分组依据。
  3. 设计出简洁的配置指定不同的集群、不同的分组。
  4. 自由地忽略不需要的操作。
  5. 安全的预览即将执行的命令,避免误操作。

我们把这个单一脚本命名为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" }
]
]
}
}

在这个配置结构里,有两层设计:

  1. 可以配置不同的子系统, 并且根据常见需求预定义内置的子系统,当某些情况下不满强需求时,可以增加子系统。
  2. 每个子系统下定义四种操作:push,start,stop,check

为什么要定义出一个叫做push的操作呢?这是因为经过分析,下层的脚本提供了四个基本的原子操作:

  • pub:将一个group拷贝到目标机器上。
  • stop: 停止目标机器上的一个group
  • start:开始目标机器上的一个group
  • check:执行测试脚本检测一个group在目标机器上的基本运行情况。

那么,我们实际在部署的过程中,stop,start,check 三个操作都相对来说比较单一,不会有太多副作用。但是,pub动作一般来说就是部署新版本的服务上去,此时:

  1. 它需要和其他的操作组合使用。
  2. 多个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选项时,才会真正执行,这样所有对自己的操作有疑惑的用户都可以放心的预览目标指令序列。

尾声

关于指令序列,有点像汇编的过程:

  1. 提供原子的指令。
  2. 提供组合原子指令的机制(例如分组)。

在程序设计的其他场景,例如测试用例的组织,异步链式逻辑的组织里,指令序列都有使用的机会。有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)的更多相关文章

  1. 控制结构(10): 指令序列(opcode)

    // 上一篇:管道(pipeline) // 下一篇:Continuation-passing_style(CPS) 发现问题 在一个正式项目的开发周期中,除了源代码版本控制外,还存在着项目的配置/编 ...

  2. 循环-10. 求序列前N项和*

    /* * Main.c * C10-循环-10. 求序列前N项和 * Created on: 2014年7月30日 * Author: Boomkeeper *******部分通过******* */ ...

  3. MariaDB 10.3 序列

    在MariaDB .3版本中sequence是特殊的表,和表使用相同的namespace,因此表和序列的名字不能相同. MariaDB [wuhan]> select version(); +- ...

  4. wannafly 练习赛10 f 序列查询(莫队,分块预处理,链表存已有次数)

    链接:https://www.nowcoder.net/acm/contest/58/F 时间限制:C/C++ 5秒,其他语言10秒 空间限制:C/C++ 262144K,其他语言524288K 64 ...

  5. 【AngularJS】—— 10 指令的复用

    前面练习了如何自定义指令,这里练习一下指令在不同的控制器中如何复用. —— 来自<慕课网 指令3> 首先看一下一个小例子,通过自定义指令,捕获鼠标事件,并触发控制器中的方法. 单个控制器的 ...

  6. Velocity(10)——指令的转义

    引用的转义使用"\",指令的转义也是使用"\".但是,指令的转义要比引用的转义复杂很多.例如: #if($foo) Go! #end $foo为true,输出G ...

  7. 【python cookbook】【数据结构与算法】10.从序列中移除重复项且保持元素间顺序不变

    问题:从序列中移除重复的元素,但仍然保持剩下的元素顺序不变 解决方案: 1.如果序列中的值时可哈希(hashable)的,可以通过使用集合和生成器解决.

  8. msvc2010生成的指令序列有问题,可能跟pgo有关

    正常序列 有问题序列 这段代码程序启动是执行,会导致崩溃 工程使用ltcg pgo,也就是说,第一次编译连接完成后,会跑一次profile,再执行连接器代码生成优化. 构建记录显示,ltcg已跑完,说 ...

  9. 循环-10. 求序列前N项和(15)

    #include<iostream>#include<iomanip>using namespace std;int main(){    double i,n,t,a,b;  ...

随机推荐

  1. SpringBoot构建RESTful service完成Get和Post

    一个基本的RESTfule service最进场向外提供的请求Method就是Get和Post. 在Get中,常用的都会在请求上带上参数,或者是路径参数.响应Json. 在Post中,常用的会提交fo ...

  2. 多态性(C#)

    在面向对象编程中继承性和多态性是重要机制,前面我为大家分享了我对“类的继承”的理解,哪么今天我就跟大家分享下我对“多态性(C#)”的理解. 首先我们先来看看多态的定义,同一操作作用于不同的对象,可以有 ...

  3. 利用angular打造管理系统页面

    1 创建一个新的angular应用 ng new adminSystem 2 利用WebStorm打开adminSystem应用 3 借助AdminLTE这个开源项目来辅助开发 AdminLTE项目: ...

  4. Java连接数据库的4中方式详解

    Java连接数据库的方式有多种:根据所需要的不同数据库驱动分,分为四种: 1:1类驱动.这就是JDBC-ODBC桥的方式. 但这种方式不适合程序的重用与维护,不推荐使用.需要数据库的ODBC驱动. 2 ...

  5. 删除多余的win10软件

    第一步:开始→所有程序→WindowsPowershell→然后右键以管理员方式运行 全部应用: Get-AppxPackage | Remove-AppxPackage 计算器: Get-AppxP ...

  6. JDK API文档中,<E>、<T>、<?>分别代表什么意思?

    Type ParameterConventionsYou have already seen the angle bracketand single letter notation used tore ...

  7. C语言指针(三)指针传递给函数

    实例1:传递一个无符号的long型指针给该函数 #include<stdio.h>#include<time.h>void getSeconds(unsignedlong*pa ...

  8. SpringMVC(四)-- 文件下载、自定义拦截器、异常处理

    1.文件下载 用ResponseEntity<byte[]> 返回值完成文件下载 具体参见本博客之前的<文件上传下载> @RequestMapping(value=" ...

  9. React渲染问题研究以及Immutable的应用

    写在前面 这里主要介绍自己在React开发中的一些总结,关于react的渲染问题的一点研究. 另外本人一直希望在React项目中尝试使用,因此在之前已经介绍过immutable的API,可以参看这里I ...

  10. python 爬取天猫美的评论数据

    笔者最近迷上了数据挖掘和机器学习,要做数据分析首先得有数据才行.对于我等平民来说,最廉价的获取数据的方法,应该是用爬虫在网络上爬取数据了.本文记录一下笔者爬取天猫某商品的全过程,淘宝上面的店铺也是类似 ...