前端流程自动化

原文刊登在githubcss

流程自动化

前端从诞生依赖能够说经历了一个从不规范到规范,再到自动化的一系列过程。最开始的时候没有前端这个领域,全部的前端职责都是由服务端来代替完成,当时的情况是很是混乱的,派别丛生,浏览器厂商规范不统一,模块机制混乱,代码风格写法各自为派,真有点群雄逐鹿的感受。从HTML5,CSS3,ES6(536)的发布开始,前端已经趋于规范化。浏览器的规范趋向一致,微软的浏览器也逐渐跟上了步伐,浏览器兼容性问题向前迈进了一大步。现在前端开发的过程也愈来愈规范,愈来愈有规律可循。所以你们在想这些有规律的东西能不可以实现自动化(程序员最喜欢偷懒),能不能将枯燥无味的工做交给电脑来作,减小重复的工做,提升工做效率。 答案固然是能够的,并且目前业内已经有很是多的成功案例。从facebook的Waitir,google的自动化流程系统,到国内阿里的def,再到小公司使用的gitlab ci,发现不少公司都已经开始了流程自动化的探索。本章我会先讲述前端的工做流程是怎样的,而后对前端工做流程进行分析,分析哪些任务能够自动化,实现自动化的思路是什么,最后我会讲述如何搭建一个自动化的小平台。html

前端工做流程

让咱们开始以前,先来看下咱们目前前端的工做流程是怎么样的,我分享的只是我我的的工做流程,不一样公司和我的可能略有不一样,但总的思路应该是差很少的,请不要太介意。前端

阶段一 需求产生 - 准备开发

这个阶段又能够分为以下三个小阶段:vue

需求评审

正常状况下,从需求产生到准备开发,应该有一个需求评审会,这时候相关开发和产品经理会汇集在一块儿,商讨需求是如何产生的(敏捷开发的需求一般是从客户的反馈中产生的),咱们作的功能有没有解决客户的问题,咱们对需求的理解有没有误差和需求的优先级等。通过完全充分的讨论以后,若是需求理解没有问题,而且需求确实能够解决客户的问题,咱们会为工做安排时间和优先级,将task列到看板(同时列到系统和白板上),并天天追踪更新。java

理解产品经理的真正意图,明白需求产生的背景node

拆分组件和模块

前端目前比较推荐的工做方法是组件式开发,即将页面拆分红足够粒度的小组件和模块。由单独的人负责相关独立模块和组件的开发,作到组件和模块的复用,这个已经在第二章讲过了,不在此赘述。react

创建分支

目前大多数互联网公司的版本管理工具都是使用git,包括咱们的公司,并且公司内部一般也有本身的git flow,咱们作新功能的时候一般会创建一个feature分支,开发完毕后合并到release分支等待测试和发布。固然就算你是SVN思路也是同样的。git

阶段二 开始开发 - 提交测试

写单元测试

这里一般有两个比较常见的问题。问题一是能不能不写单元测试,或者说不写单元测试有什么很差的影响?第二个问题是为何要先写单元测试而不是先写代码?咱们对这两个问题一一进行解答。先说第一个问题,其实不写单元测试是很容易作到的,就好像咱们不努力锻炼身体同样简单,可是当咱们面对本身的一大块腹肌的时候,是否是有那么一丁点儿后悔呢?单元测试也是同样,当你开始写的时候,没有任何收效,据统计写单元测试的时间要高于写代码的时间,那么咱们为何还要“白”花那么多时间写测试用例呢?那是由于当随着应用规模逐渐扩大,复杂度逐渐上升的时候,完善的测试用例,给你修改代码的勇气。当单元测试显示all pass的时候,仿佛有人跟你说“干吧,哥们,没毛病。”。你不会由于怕改坏了代码而蹑手蹑脚。但这里要强调一点的是,覆盖率低的单元测试不但不可以起做用,反而会给人一种“干吧,哥们,没毛病。”的假象。这也是为何我后面强调使单元测试覆盖率足够高的缘由。咱们再来看下第二个问题,这个问题涉及到一个概念叫测试驱动开发(TDD)。TDD的理念就是先写测试用例,而后写具体实现。TDD的一个重大优势就是你将具体实现放到后面,这样你就不会深陷细节的泥潭,你就拥有更清晰的视野,你会对业务或者逻辑理解更佳深入。这就好像你看过不少高手写代码,它们会先将思路写下来,而后再写代码是一个道理。程序员

回答了关于测试的常见问题,咱们来看下单元测试究竟怎么写。其实也简单,单元测试一般来自于测试人员整理的测试用例,固然一些特殊的算法逻辑须要本身整理啦。一个原则就是能用测试同窗就用测试同窗,不用白不用,对吧?可是前端写单元测试的时候,总会感受很困难,我一开始也是这么以为的。后来我接触了函数式编程,整我的就感受豁然开朗。传统的面向对象编程,命令式编程有一个很是大的弊端,引用Joe Armstrong(Erlang语言的创造者)的一句话就是:github

你想要一个香蕉,但获得的倒是一个拿着香蕉的大猩猩

没错,当我回顾我好久以前的代码,的确如此。这也不能彻底怪咱们,咱们已经习惯了各类假设,各类外部依赖。咱们将这些变成理所固然,咱们不断地改变状态的状态,致使状态难以追踪。所以咱们写单元测试很是困难。当你开始以函数式编程理念写代码的时候,你会发现代码很是好测试。好比目前比较流行的react的展现组件,就是一个纯函数,对这样的组件进行测试就很简单。当你研究redux的代码的时候,你会发现redux的reducer设计的精妙,他将reduce在空间上的抽象变成reducer在时间上的抽象,而且reducer纯函数的理念也让代码更容易测试,具体能够看个人这篇博文

防护性编码

写代码以前要先想有没有轮子可用。若是没有在开始造轮子。我喜欢将逻辑划分为多个函数,而后在函数开头对入参进行校验,并对每一步可能出错的地方进行校验,典型的是npe(null pointer exception)。这就是典型的防护性编程。

// String a -> Number b -> Boolean
function testA(a, b) {
   if (!a) return false;
   if (!isString(a)) return false;
   if (!b) return false;
   if(!isNumber(b)) return false
   return !!a.concat(b);
}

复制代码

这种方法在前端尤为有效,由于js是动态语言,若是你不使用typescript等增长静态检测的功能的话,代码会变得脆弱不堪。一个简单的作法就是,假设每一行代码都会遇到异常状况,都会报错。

使单元测试覆盖率足够高

前面讲了单元测试的重要性,以及单元测试的写法思路。这里强调一下单元测试的覆盖问题,低的单元测试覆盖率毫无用处,甚至会起副作用,所以保持足够高的单元测试覆盖率显得很是重要,业界广泛承认单元测试覆盖率在95%以上是比较合适的。

阶段三 测试完毕 - 可发布状态

这个阶段咱们的代码已经通过了自测和测试人员的回归,咱们认为能够发布上线了。一般咱们还会让产品经理进行验收,看看是否是他们想要的效果。

编译

目前写的代码是在多个文件的,咱们的代码使用了不少浏览器不支持的特性,咱们须要将css从js中提取出来等等。这些工做都须要经过编译来完成。

包分析

咱们的项目的代码是由许许多多的依赖构成的,咱们会依赖一些框架如react,vue,咱们会使用一些工具库如lodash,ramdajs,mostjs等。这些都构成了项目的不稳定和不肯定。这也就是为何大公司如阿里,会对外部依赖有着很强的执念。npm也察觉到了这一点,以致于如今的npm在安装事后都是锁定版本的。但这依旧没法保证依赖包的质量。 一个查看包质量的原则就是文档足够丰富,单元测试覆盖率足够高,受欢迎(start足够多),虽然上述条件不是一个库质量良好的充分条件,但倒是必要条件,咱们能够经过它筛选一大批不合格的库。

代码检查

咱们会对代码进行检查,有没有语法错误等。这一步能够经过eslint检查,也能够经过flow或者typescript这样的静态检查工具检查。总之,这一步是检查有没有错误代码或者不符合规范的代码。

代码优化

代码已经经过了检查,这个时候咱们须要对代码进行优化,好比压缩,合并,去空格去console等,或者提取公共依赖,再或者删除僵尸代码(tree shaking)。

CodeReview

咱们会组织相关人员进行代码评审,确保代码质量。这部分是人工完成,是对前面工做的最后把关。

阶段四 准备发布 - 发布完成

发布静态资源到CDN

咱们资源发布到CDN等待最终的发布,保证版本发布以后,用户能够直接得到最新的CDN资源。

打tag

咱们将代码打tag,一个个tag就像是一个个里程碑。 当咱们须要对某一个版本代码进行修复的时候,tag的做用就显示出来了。

修改线上版本号

咱们的功能已经达到了能够发布的状态,而后咱们会对版本进行发布。修改线上的版本号,这样咱们的用户就能够访问到咱们最新写的代码了。

阶段五 发布上线 - 线上验证

咱们已经将代码发布上线了,一般咱们须要验证下代码是否正确发布,有没有影响线上其余功能。

上面的过程多是大多数互联网公司的工做流程了。那么下一节我会对每个阶段进行分析,找出能够自动化的点,并讲述自动化的技术思路是怎样的。

实现流程自动化的思路是什么

上面讲述了常规的需求产生到功能发布的完整过程,经过上面的分析,咱们发现阶段三和阶段四是能够高度自动化的,阶段三和阶段四进行作了什么事,为了方便你们的理解,我整理了一个图:

图3.1

图中的虚线表示自动完成,无需人工。实线表示须要人工操做。图的中心点是dev,能够看出dev的操做有三个,分别是提交(commit)打tag,以及提交pr(pull request)。不一样的操做会触发不一样的钩子,完成不一样的操做。咱们一个节点一个节点进行分析,它们分别作了什么事,以及设计实现的思路。图中须要实现的系统有三个,第一个是包分析引擎(package analyser),第二个是CI中心,第三个是CD中心,咱们分别来看。

package analyser

包分析工具这里能够是前端的npm包分析,也能够是后端的好比maven包分析。这里以npm包分析为例,maven等其余包分析同理,只是具体技术实现细节不一样,npm包分析和maven包分析只是具体策略不一样,咱们能够经过策略模式将具体的分析算法封装起来。首先看下包分析引擎实现的功能,其实包分析引擎就是分析应用依赖的包,并逐个递归分析其依赖包,找到其中有风险的依赖,并通知给使用者(项目拥有者)。具体功能包括但不限于分析有安全风险的包,提示有补丁更新,有了这些依赖数据,咱们甚至能够统计公司范围内包的使用状况(包括各个版本),这些数据是颇有用的。后面一节咱们会具体分析包分析引擎的实现细节,使读者能够自行搭建一个npm包分析引擎。

CI

CI(Continuous Integration)是持续将新功能集成到现有系统的一种作法,极限编程也借鉴了CI的基本思想。那么咱们是怎样使用CI的呢?我刚才在图中也体现了,开发者的提交会触发CI,CI会作一些单元测试,代码检测等工做,若是不经过则反馈给相关人员,不然将经过的代码合并到库中。也就是说CI并非一项技术,二是一种最佳实践,更多能够参考wikipedia。后面我会介绍实现一个CI的基本思路。

CD

CD(Continuous Delivery)一样也是一种最佳实践,并非一项技术。它的基本思想是保证代码随时可发布,它保证了代码发布的可信赖性,同时持续集成减小了开发人员的工做量。更多能够参考wikipedia。后面我会介绍实现一个CD的基本思路。

固然咱们的系统还比较不完善,咱们还能够增长配置中心,方便对版本进行管理,还能够增长监测平台(第四章有讲),你们能够发挥本身的聪明才智。

咱们还漏了什么

上面讲述了一个需求产生到功能上线的过程,咱们也分析了若是进行自动化。咱们还忽略了一个项目初期的一个过程,就是搭建脚手架的过程。如何将搭建脚手架的过程也自动化,配置化,最好还能在公司范围内保持一致性。

脚手架作了什么

当咱们开始一个新项目的时候,要先进行技术调研和选型,当肯定了技术方向的时候,咱们须要一个架子,项目成员能够根据这个架子写代码。这个架子能够手工生成,很早以前我就是这么干的,可是手工生成的有不少缺点。第一就是效率低,第二就是不利于统一。为了解决这个问题,咱们引入了云脚手架的概念。要明白云脚手架咱们须要先知道脚手架。咱们先来看下脚手架作了什么事,简单来讲脚手架就是生成项目的初始代码。咱们经过目前比较火的react的配套脚手架工具create-react-app(如下简称cra)来认识一下脚手架的工做原理。咱们先来看下cra的使用方法:

npm install -g create-react-app

create-react-app my-app
cd my-app/
npm start
复制代码

这样咱们就有了下面的项目结构:

图3.2

若是要实现以上功能,一个最简单的方法就是执行create-react-app xxx 以后去远端下载文件到本地。其实cra的思路就是这样的,我用一张图来表示cra的基本过程:

图3.3

每一步的实现也比较简单,你们能够直接查看源码

中心化,可配置的脚手架服务

上面介绍了脚手架的做用和实现原理。可是上述的脚手架没法实现中心化,也就是说不一样团队没法造成相互感知,具体来讲就是不一样项目的脚手架是不一样的或者不可以实现高度定制化,所以咱们须要构建一个中心化,可配置的脚手架服务,我称之为云脚手架。脚手架采用CS架构。客户端能够是一个CLI,服务端则是一个配置中心,模块发现中心。以下图是一个云脚手架的架构图:

图3.4

客户端经过命令告诉服务端想要初始化的模版信息,服务端会从模板库中查找对应模板,若是有则返回,没有则请求npm registry,若是有则返回并将其同步到模板库中,供下一次使用。若是没有返回失败。 这样对不一样团队来讲,脚手架是透明的,团队能够定制适合本身的脚手架,上传到模板库,供其余团队使用,这就造成了一个闭环。

如何搭建一个自动化平台

搭建package analyser

我将包分析引擎的工做过程分为如下三个阶段

创建黑白名单

咱们须要对包分析,分析的结果固然须要数据支撑。所以黑白名单是不可少的,咱们能够本身补充黑白名单,咱们甚至能够创建本身的黑白名单系统,固然也能够接入第三方的数据源。无论怎样,第一步咱们须要有数据源,这是第一步也是最重要的一步。为了简单起见,我以JSON来描述一下咱们的数据源:

// 全部的key都是npm包的包名
{
 "whiteList": ["react", "redux", "ant-design"],
 "blackList": {
   "kid": ["insecure dependencies 'ssh-go'"]
 }
}

复制代码

递归分析包并匹配黑白名单

这一步须要递归分析包。 咱们的输入只是一个配置文件,npm来讲的话就是一个package.json文件。咱们须要提取package.json的两个字段dependencies和devDependencies,二者就是项目的依赖包,区别在于后者是开发依赖。这个时候咱们能够得到项目的一个依赖数组。形如:

const dependencies = [{
   name: "react",
   version: "15.4.2"
 }, {
   name: "react-redux",
   version: "5.0.3"
 }]
复制代码

而后咱们须要遍历数组,从npm registry(能够是官方的registry, 也能够是私有的镜像源)获取包的具体内容,并递归获取依赖。这个时候咱们获取了项目全部的依赖的和深层依赖的包。最后咱们须要根据包名去匹配黑白名单。咱们还有一步须要作,就是获取包的更新日志,将有意义的日志(这里能够本身封装算法,究竟什么样的更新日志是有意义的,留给你们思考)输出给项目拥有者。

结果输出

咱们已经将全部的依赖包进行匹配,这个时候已经知道了系统依赖的白名单包,黑名单包和unknown包,咱们有了匹配以后的数据源。

const result = {
    projectName: "demo"
    whiteList: ["react", "redux"],
    blackList: [{name: "kid", ["insecure dependencies 'ssh-go'"]}],
    changeLog: [{name: ""react-redux, logs: {url: '', content: ''}}]
 }
复制代码

咱们要作到数据和显示分离。这个时候咱们将数据单独存起来,而后采用友好的信息展现出来,这部分应该比较简单,很少说了。

搭建持续集成平台

若是想要搭建持续平台的话,最基础的三个服务是要有的,lint,test以及report。其实lint,test和report的具体实现已经超出了CI的范畴,这里就大体讲如下。对于lint来讲,本质上是对js文本的检查,而后匹配一些规则,业界比较有名的是红宝书的做者nzakas的eslint,关于eslint的总体架构能够查阅这里zakas的初衷不是重复造一个轮子,而是在实际需求得不到JSHint团队响应的状况下,本身开发并开源了eslint:一个支持可扩展、每条规则独立、不内置编码风格为理念编写一个js lint工具。对于test,其实就是运行开发人员写的测试用例,并保证运行正确且覆盖率足够高。若是上述步骤出错,则会向相关人员告警。下面是CI的架构图:

图3.5

代码会通过lint,途中会从配置中心拉取项目的presets和plugins,经过后进入下一个流水线test,test会将代码分发到浏览器云中进行单元测试和集成测试,并将结果发给相关人员,上述两个步骤若是出错也都会经过report service发送信息给相关人员。

搭建持续部署平台

持续部署在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「类生产环境」(production-like environments)中。好比,咱们完成单元测试后,能够把代码部署到链接数据库的 Staging 环境中更多的测试。若是代码没有问题,能够继续手动部署到生产环境中。所以持续部署最小的单元就是将代码划分为一个个能够发布的状态。下面是一个经典的企业级持续部署实现:

图3.6

https://continuousdelivery.com/implementing/architecture/

能够看出持续集成,实际上是将新模块融合到系统里面,造成一个可发布单元,它自己不涉及到发布的流程。真正将代码发布到线上,仍是须要人工来操做。

能够经过配置中心实现代码发布

要想实现持续部署的架构,是须要代码和系统架构的配合,这是和前面我讲述的其余系统不同。持续部署要求代码的耦合度要足够低,尽可能少地影响其余模块。每当发布一个新功能的时候,不须要将所有代码测试回归,而是采用mock,stub等方式模拟外部依赖。目前比较流行的微服务,也是一种将代码解耦合的一种实践,它将系统划分为若干独立运行的服务,服务之间不知道彼此的存在,甚至服务之间的语言,技术架构都不相同。前面的章节讲述了组件化和模块化,在这里又能够看出组件化和模块化的重要性。

更多关于持续部署实现的介绍

搭建通知服务等其余可接入的第三方服务

前面反复提到了反馈系统feedback。可能咱们还须要其余第三方系统,好比数据可视化系统等。这里以接入通知服务为例,讲解如何接入一个通用的第三方系统。在谈通知服务具体细节以前,咱们先来说下接入一个第三方服务须要作什么。本质上接入不一样的服务是服务治理的范畴,而目前服务治理当属微服务占据上风。这种经过不断接入“第三方”服务的方式使得业务和应用分离。在微服务以前,你们广泛的作法是将不一样的系统作成不一样的应用,而后经过某些手段进行通讯。这种方式有一个显著的缺点就是应用有不少重复冗余的逻辑和代码。而微服务则不一样,微服务将系统拆分红足够小的块,这样可以显著减小冗余。服务化有如下特色:

  • 应用按业务拆分红服务
  • 服务可独立部署
  • 服务可被多个应用共享
  • 服务之间能够通讯

这里并不打算讨论服务治理的具体实施细节,可是须要明白的是经过这种微服务的思想。咱们须要通知服务,只须要发送一个信号,告诉通知服务,通知服务返回一个信号,表示输出的结果。好比我须要接入邮件服务这个通知服务。代码大概是这样的:

'use strict';
const nodemailer = require('nodemailer');
const promisify = require('promisify')

// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
exports default mailer = async context => {

    // create reusable transporter object using the default SMTP transport
    let transporter = nodemailer.createTransport({
        host: 'smtp.ethereal.email',
        port: 587,
        secure: false, // true for 465, false for other ports
        auth: {
            user: account.user, // generated ethereal user
            pass: account.pass  // generated ethereal password
        }
    });

    // setup email data with unicode symbols
    let mailOptions = {
        from: '"Fred Foo 👻" <foo@blurdybloop.com>', // sender address
        to: 'bar@blurdybloop.com, baz@blurdybloop.com', // list of receivers
        subject: 'Hello ✔', // Subject line
        text: 'Hello world?', // plain text body
        html: '<b>Hello world?</b>' // html body
    };

    // send mail with defined transport object
    await promisify(transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
            return console.log(error);
        }
        console.log('Message sent: %s', info.messageId);
        // Preview only available when sending through an Ethereal account
        console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));

        // Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@blurdybloop.com>
        // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
   
    }));
          
    return {
           status: 200,
           body: 'send sucessfully',
           headers: {
               'Foo': 'Bar'
           }
      }
};
复制代码

若是每个应用须要使用邮件服务,就须要写这样的一堆代码,若是公司的系统不一样致使语言不一样,还须要在不一样语言都实现一遍,很麻烦,而若是将发送邮件抽象成通知服务的具体实现,就能够减小冗余代码,甚至java也能够调用咱们上面用js写的邮件服务了。

小提示。当咱们须要使用邮件服务的时候,最好不要在代码中直接向邮件服务发送消息,而是向通知服务这种抽象层次更高的服务发送。

有一个流行的概念是faas(function as a service)。它每每和无服务一块儿被谈起,无服务不是说没有服务器,而是将服务架构透明,对于普通开发者来讲就好像没有服务器同样,这样就能够将咱们从服务器环境中解放出来,专一于逻辑自己。fission(Fast Serverless Functions for Kubernetes)是一个基于k8s的无服务框架。经过它开发者能够只关注逻辑自己,咱们能够直接将上面的mail方法做为部署单元。下面是一个例子:

$ fission env create --name nodejs --image fission/node-env

  $ curl https://notification.severless.com/mailer.js > mailer.js

  # Upload your function code to fission
  $ fission function create --name mailer --env nodejs --code mailer.js

  # Map GET /mailer to your new function
  $ fission route create --method GET --url /mailer --function mailer

  # Run the function. This takes about 100msec the first time.
 
  $ curl -H "Content-Type: application/json" -X POST -d '{"user":"user", "pass": "pass"}' http://$FISSION_ROUTER/mailer

复制代码

这样若是有一个系统须要将mailer服务切换成sms服务就很简单了:

$ fission env create --name nodejs --image fission/node-env

 $ curl https://notification.severless.com/sms.js > sms.js

 # Upload your function code to fission
 $ fission function create --name sms --env nodejs --code sms.js

 # Map GET /mailer to your new function
 $ fission route create --method GET --url /sms --function sms

 # Run the function.  This takes about 100msec the first time.

 $ curl -H "Content-Type: application/json" -X POST -d '{"user":"user", "pass": "pass"}' http://$FISSION_ROUTER/sms

复制代码

服务的实现也足够简单,只须要关心具体逻辑就OK了。

自动化脚本

哪些地方应该自动化

上面讲述了软件开发的过程,以及咱们能够将哪些过程自动化。这一节,咱们讲述自动化的第二部分自动化脚本。刨除软件开发自己,计算机中其实也充满了重复性工做,一样也充满了解决这些重复工做的自动化解决方案,这些解决方案能够是一个脚本,能够是一个软件或者插件等,总之它将人们从重复性的工做中解脱了出来。举个例子,咱们都有过下载视频的经历,咱们看上了某个网上的一个视频,咱们想下载下来,可是在下载的时候,发现只有VIP能够下载。咱们就去网上查找解决方案。咱们按照教程历经千辛万苦终于将视频下载了下来。下次咱们又要下载视频了,咱们还要经历了上面的步骤(咱们甚至还要再看一遍教程)。因而自动下载在线网站视频的自动化解决方法出现了,人们只须要简单的操做就能够将本身心爱的视频下载下来,多么省心!相似的还有不少,好比批量处理工具,一键重装系统工做等等,根本数不过来。

本质上,任何应该自动化的都应该被自动化。只要是重复性工做,都应该将其自动化。为何电脑能够处理的东西,非要人工来处理呢?开发者应该将精力花费在更值得花费的地方,一些有创造性的工做上。上面介绍了普通用户的例子,如今我举一个工做中的例子。好比以前个人公司的发布,就是一个简单的流程,每次发布都要按照流程执行,这就很适合去自动化。咱们当时的发布流程是这样的

git tag publish/版本号 
git push origin publish/版本号
复制代码

而后去cdn(https://g.alicdn.com/dingding/react-hrm-h5/version/index.js) 上查看是否发布成功。 我以为这已经花费我必定的时间了,并且新来的同窗不得不学习这种繁琐的东西。 为何不能自动化呢? 借用墨菲定律下, 该自动化的终将实现自动化。 自动化真的不止是减小时间,更重要的是减小出错的可能。 软件工程领域有这么一句话,减小bug有两种方式,一种是少写代码以致于没有明显bug。 二是写不少代码以致于没有明显bug。

少写代码,少作重复的事,这是个人信条。

若是你认真观察的话,你会发现能够自动化的东西实在是太多了。当你真正将自动化贯彻到实际编码生活中去的时候,你会发现你花费在重复工做上的时间在缩少,而且你的幸福感会提高,你会颇有成就感。以前看过一个文章,文章里面说它会将任何能够自动化的东西自动化(不只限于工做),它会编码控制若是在晚上下班的时候,本身的session还在的话(还没下班),就发自动给老婆发短信,短信内容是从预先设置好的短语中随机选取的。

以前我在微博上看到一个研究,它经过分析女友的微博,来分析女友的情绪。我以为上面发送短信的时候,若是能够根据老婆的情绪对应智能回复不一样内容会更棒。

运维

若是你们有运维经验的话,会知道运维常常须要开启,中止,重启服务。这些工做有很强的规律性,很适合作自动化。幸运的是,咱们借助shell,能够与操做系统深度对话,轻松实现上面提到的运维需求。下面是一个shell脚本实现服务启动,中止和重启的例子。

#! /bin/sh
DIR= `pwd`;
NODE= `which node`

// 第一个参数是action,是start,stop和restart中的一个。
ACTION=$1

# utils
get_pid() {
   echo `ps x|grep my-server-name  |awk '{print $1}'`
}

# 启动
start() {
 pid= `get_pid`;
 
 if [ -z $pid ]; then
   echo 'server is already running';
 else
   $NODE $DIR/server.js 2>&1 &
   echo 'server is running now'
 fi
}
// stop和restart代码省略
case "$ACTION" in
   start)
      start
   ;;
   stop)
      stop
   ;;
 esac
复制代码

借助shell强大的编程能力,还有将数据抽象成流,并经过组合流完成复杂任务的能力,咱们能够构建很是复杂的脚本。咱们甚至能够将检测三方库潜在风险信息功能作成脚本,而后集成到CI中,只有想不到,没作作不到。这里只是给你们提供思路,但愿你们能够根据这个思路进行延伸,从而将一切应该被自动化的东西所有自动化。

开发流程自动化

上面讲述了哪些地方应该被自动化。你们看了后极可能是拿了锤子的疯子,你会发现全部的东西都是钉子,是否是任何东西均可以自动化?固然不是! 能够自动化的应该是有着极强规律的枯燥活动,而充满创新的任务仍是要让开发者本身享受。所以当你以为某项工做枯燥无味,而且有着很强的规律和判别指标的时候就是你拿起手里锤子的时候。好比我天天都要回报个人工做,将本身的进度同步给组里其余人,虽然很枯燥乏味(但愿个人领导不会看到),可是并无规律性。所以不该该被自动化。

这里再举一个例子。我将开发过程当中须要处理的事情进行了分类,我称为元脚本(meta-script),分别有以下内容:

  1. concat-readme (将项目中全部的readme组合起来,造成一个完整的readme)

  2. generate-changelog (根据commit msg 生成 changelog)

  3. serve-markdown (根据markdown生成静态网站)

  4. lint (代码质量检测)

  5. start server (开启开发服务器)

  6. stop server (中止运行开发服务器)

  7. restart server (重启开发服务器)

  8. start-attach (attach到浏览器,以在编辑器中进行调试)

每个meta-script都是一个小的脚本或者一个外部库(external library),因为我使用的是npm做为包管理工具,所以我将meta-script放到了package.json文件中的script里面。这样我就能够经过运行npm run xxx 执行对应的脚本或者外部库了。可是别忘了,有时候咱们须要作一些复杂的任务,好比我须要在浏览器中查看项目中全部的readme。那么我须要先把项目中的readme所有concat起来,而后将concat的内容做为数据源,传给serve-markdown,而后serve-markdown传给start-server。代码大概是这样的:

npm run concat-readme > npm run serve-markdown > npm run start-server --port 1089

复制代码

我称上面的代码task,而后咱们把上面的代码也放到package.json的script中,彷佛这种作法很好地解决了问题。可是它有几个缺点。

  • meta-script 和 task 混杂在一块儿,这样自己并不可怕,可是此时并非全部的script均可以很好重用,咱们的task并非为了重用。
  • package.json是做为版本控制的一部分存在,若是某个开发者但愿根据本身的状况定制一个task,就不该该放到这里了。

所以个人作法是将meta-script放到版本库(这个例子咱们放到了package.json中),而后将task放到编辑器中控制。我使用的编辑器是VSCODE,它有一个task manager功能,也能够下载第三方插件进行扩展。而后咱们能够本身定义task,好比上面的咱们能够做为我的配置保存起来,命名为start doc-site。

咱们能够继续组合:

npm run changelog > npm run serve-markdown > npm run start-server --port 1089

复制代码

咱们经过meta-script又增长了一个很好用的task,咱们能够命名为start changelog-site。

还有更多:

npm run stop > npm run start-attach

复制代码

咱们又实现了一个“放弃”浏览器调试,而用editor调试的task,咱们称之为editor-debug。

咱们能够增长更多的meta-script,咱们能够根据meta-script组合更多task。而后咱们只须要one-key就能够实现任意中组合的功能,是否是很棒?本身动手试试把!

无处不在的自动化

上面举的例子是一个典型的开发流程,那么其余平常的自动化怎么去作呢?好比我要控制电脑发送邮件,好比我要控制电脑睡眠,我要调整电脑的音量等。虽然咱们也能够按照上面的思路,写一个元脚本,而后将元脚本组合。可是这里的元脚本彷佛并不能经过node的cli程序或者shell脚本实现。可是这刚好是自动化必不可少的一环。所以咱们面临一个GUI的自动化运行过程,将咱们繁琐重复的UI工做中解脱出来。这里介绍在mac下自动化的例子。

JXA

说到mac中自动化,jxa是一种使用javaScript与mac中的app进行通信的技术。经过它开发者能够经过js获取到app的实例,以及实例的属性和方法。经过jxa咱们能够轻松经过JavaScript来自动化脚本完成诸如给某人发送邮件,打开特定软件,获取iTunes的播放信息等功能。下面举个发送邮件的例子:

const Mail = Application("Mail");
 
const body = "body";

let message = Mail.OutgoingMessage().make();
message.visible = true;
message.content = body;
message.subject = "Hello World";
message.visible = true;
 
message.toRecipients.push(Mail.Recipient({address: "a@duiba.com.cn", name: "zhangsan"}));
message.toRecipients.push(Mail.Recipient({address: "b@duiba.com.cn", name: "lisi"}));
 
message.attachments.push(Mail.Attachment({ fileName: "/Users/lucifer/Downloads/sample.txt"}));
 
Mail.outgoingMessages.push(message);
Mail.activate();

复制代码

有两种方式运行上面的例子,一种是命令行方式,另外一种是直接做为脚本运行。

  1. 命令行方式运行
osascript -l JavaScript -e 'Application("iTunes").isrunning()'

复制代码
  1. 做为脚本运行

一种方式是保存为文件以后运行

osascript /Users/luxiaopeng/jxa/hello.js

复制代码

另外一种是用苹果自带的脚本编辑器:

图3.8

运行以后效果是这样的:

图3.7

jxa提供了丰富的api供咱们使用。详细能够查看脚本编辑器-文件-字典:

图3.9

好比我要查看dash的api:

图3.10

可是遗憾的是并非全部的app都提供了不少有用的api,好比钉钉,也并非全部的程序都有字典,好比qq,微信。好消息是mac自带的程序接口仍是比较丰富的。可是咱们发现尽管如此,咱们想要实现某些功能,仍是会比较复杂。对于不想太深刻了解而且想要自动化的开发者来讲一款简单的工具是有必要的,下面我介绍一款在mac下的神器。

Alfred

JXA的功能很是强大,可是其功能比较繁琐。若是你只是想简单地写一个自动化脚本,作一些简单的了解。介绍你们一个更加简单却不失强大的工具-alfred- workflow。 你能够自定义本身的工做流,支持GUI,shell脚本甚至前面提到的jxa写工做流。其简单易用性,以及其独特的流式处理,各类组合特性使得它功能很是强大。下面是个人alfred-workflow:

图3.11

你能够像我拆分开发流程同样将你的工做流拆解,每一部分实现本身的功能,设置语言能够不同。好比处理用户输入用bash,而后bash将输入流重定向到perl脚本等都是能够的。 alfred-workflow能够容许你简单地添加文件操做,web操做,剪贴板等,设置不用写任何代码。

图3.12

总结

本章经过前端工做流程入手,讲解了前端开发中的工做,而且试图将其中能够自动化的步骤进行自动化集成。而后讲述了完善的一个自动化平台系统是怎样的,以及各个子系统实现的具体思路是怎样的,经过个人讲解,我相信你们应该已经理解了自动化的工做内容,甚至能够本身动手搭建一个简单的自动化平台了。可是程序员中的自动化远不止将实现需求的流程自动化,咱们还会搞一些提升效率的小工具,本质上它们也是自动化。只不过他不属于工程化,在本书的附录部分,我也会提供一些自动化小脚本。

相关文章
相关标签/搜索