相信大家都有去图文制作、照相馆这些地方拍证件照的经历。在这些专业的场所,他们从采集照片,到出照片不到10分钟。其间,有多名工作人员参与其中:当我们从摄像棚里拍完回到大厅,制图师已经将我们的照片导入到软件里开始工作了:校准颜色、按规格截取大小、按版型重复,等一系列的工序完成,我们拿到照片时竟然欣喜地发现皮肤暗斑竟变得光洁一新了。

在惊叹他们工作高效的同时,我们不禁感叹:“我自己操作图片软件的水平断然不可能有他们这样的水准的。” 其实其他行业的人看待软件开发人员,也是这一样的印象。有那么多人以为软件工程师是修电脑的,大概与这样“盲目的崇拜”是类似的原因。之所以说这种崇拜是盲目的,是因为我们自己知道不少工作并没有那么神秘,稍微了解你就可以发现他也就只是一直在重复一套流程式的工作而已。上面说的制图的过程,以及我们软件开发领域的很多工作都是这种重复的流程。不同的人的区别在于,他有没有知道这个流程,以及对这个流程的熟练程度如何。

真实的重复

前段时间我和同事们为客户设计了一个工作坊(Workshop,演练实操式互动交流学习),获得不错的反馈。其他同事了解到我们的故事,也想在更多场合再来实施这个工作坊,我自己服务的客户也想把这个工作坊带到其他团队中。我不得不一个个跟他们解释这个工作坊怎么准备,需要几台服务器,分别安装什么软件……还没开始介绍课程内容就花了一个小时去讲环境准备。也就是说,其实准备过程一点儿也不重要,同事们需要的只是一个能帮助开展工作坊的环境而已!

我开始思考,如果将来再有类似需求,我怎么让这个过程更轻便,让别人快速了解这个安装过程,更轻松地配置环境,从而能够将更多的心思放在课程本身的优化上。这样一来,将环境准备过程让机器去做(比如做成一个软件,或者一个脚本)的想法就自然而然了。以后有人再有需求的时候,我就一个文件甩过去——就算我休假了,别人依然能继续开展工作。

如果把视线转回到软件开发领域,我们很容易能够发现大量类似上面这样的重复工作:

  • 需求文档一遍遍地改,一会儿发一个新版本的文件到群里,开发人员和测试人员却不知道他们改了哪里,只好将整个文档(或某一部分)看了一遍又一遍
  • 在一个类里,声明大量的用来只作 getter 和 setter 用的 field,每次多加一个公共属性都要加两个方法和一个字段。
  • 调试程序时,需要一遍遍地部署代码到应用容器中,一个个取消勾选上次加的那些断点,并附加到目标进程。
  • 每次发布版本时,要把代码打包、复制到服务器上,设置服务器使用新版本的程序
  • 每周对数据库进行备份,找一个在线用户少的时间段,将数据库切换成只读状态,然后等待同步程序运行完成,再打开写开关。
  • 每隔一年更新一次网站的安全证书,去 CA 申请证书,把收到的证书传到服务器上,挨个网站替换掉旧的证书。
  • ……

还有举不胜举的例子,这些重复分布在软件开发的整个生命周期过程中。

重复只是无趣吗?不,重复还意味着浪费!重复的流程如果可以更快而我们没有去使用这些更快的方法,难道不就是一种浪费吗?这就好比,如今从北京坐高铁去上海只需要5小时,而有人明知只需要5小时却非要花一个月骑马过去。如果不是出于成本的考虑,也不是要欣赏沿途美景,那着实就是一种浪费。

写代码人都知道,重复去做一件事除了效率低之外,还会因为人本身的不稳定性导致每次的结果不稳定。即使是奥运会上最出色的汽枪运动员也不能确保每发都能中九环以上,何况普通人?

机器更擅长重复

上面讲到由人来重复做一件事的两个典型问题,即低效和不稳定。从这两方面来考虑就很容易发现,无论一个人对一个流程如何熟练和高效,都比不上机器——机器被发明出来不就是为了帮助人类高效地完成那些重复的工作吗?

也就是说,如果一个流程需要重复地做,就应该交给机器来做:

  1. 机器效率高:拿机器与人来比运算效率,本来就不是一件公平的事。效率高带来的好处不光是快,问题是如果一件事由人来做、效率慢了,那么在指定时间里数量上就上不来(否则成本就顾不上),这样往往到最后可观测到的效果就是产量的降低(速度慢)、质量的降低(测试少)。
  2. 机器精确度高:机器只会按照既定的步骤机械地运行。机器不像人一样会忘记细节、会做错——如果机器错了,那一定是人写的代码写错了…… 于是我们可以定下一个目标,消灭一切重复工作——把它们交给机器来做,而人只做那些创造性的工作——这也正是人类擅长的部分,暂时机器还不能可靠地代替人类来做创造性的工作。

我们来看看机器可以如何帮忙我们高效搞定上一小节中的重复工作:

  • 在交流和反馈的过程中,使用 Word 修订功能来跟踪变更;在有多个版本文件时,使用 Word 的自动比较差异的功能,自动显示文档中的差异。使用 Markdown 文件,并将文件保存在版本管理工具中,能够随时查看多个版本的差异比较。
  • 使用更简洁的编程语言,比如 C# 就不需要写大量没意义的 getter 及 setter 方法。还可以使用 IDE 提供的功能自动生成常见的代码片断(下图),如果能记住这些操作的快捷键还能更快。
  • 使用自宿主模式来开发,不要依赖于别的应用程序。这样每次调试就是启动一个独立进程,结束调试时进程就终止了,不存在重复部署代码的问题。可以使用 IDE 提供的功能,外加快捷键可以快速禁用、删除所有断点。
  • 使用自动化部署的工具,将程序代码的打包、传输和部署的过程都自动化起来!
  • 数据库备份和证书维护,都可以借助脚本来自动完成了。各数据库都支持多种编程接口,备份工作的脚本化很容易做。对于域名证书的申请和更换,自从 Let’s Encrypt 出现之后,也能脚本化了。

image

无论是 Word 中的自动比较差异,还是后面几项里自动触发的工作,都是利用机器来将一些固定的流程化的工作进行自动化的过程。 那么,让电脑代替我们来做重复的事,或者说自动化,是一件很难的事吗?我们日常用的各种软件本身就是一种自动化的表现形式。比如,Office 软件里就有不少自动化功能,像 Word 里自动生成页码和文档目录,以及在 Excel 里自动求和求平均数等。在软件工程领域,聪明的开发者们当然会制造一些工具来优化这个体验了。过去有不少人喜欢用 CodeSmith 之类的代码生成工具,也是自动化的一种体现。

用代码描述自动化

开发者最擅长的还要数代码了,代码本身就是天然的自动化。不少开发人员习惯了为别人开发用于改善自动化体验的软件,却往往忘了自己在开发过程中也有大量的工作同样需要自动化来优化。非常幸运的是软件开发人员并不需要哀叹“遍地罗绮者,不是养蚕人”,我们自己就可以用软件来优化自己的体验。

来看一小段脚本,设定一个定时任务,两小时(7200秒)之后关闭计算机:

1
shutdown /s /t 7200

这个脚本虽然短小,却很有用,它的功能类似于帮我们完成“点击开始菜单-点击关机”的功能。在迅雷增加“下载完关机”功能之前,我一直用它确保在睡前下载任务完成之后,电脑能自己关机。它实现了我一个关键诉求:无人值守——我不想一直守在电脑前面等着它下载完毕,再手动关机。(自从网络提速了、迅雷流氓了之后,我早就不用迅雷了,由此可见这段脚本有多古老了!)

从上面这段脚本中可以看出,自动化任务不光可以做重复性工作,还可以带一些触发条件,比如上面的“时间到”。试想,如果电脑能自动在重要节日帮忙订好礼物,不少宅男忘记纪念日的问题不就不会出现了?Windows 电脑中内置有“计划任务”工具,提供了完整的按时执行指定自动化任务的功能。命令行工具方面,Windows 上提供了 at,非 Windows 上提供了 cron,都能用于定时运行一个任务。

这样看来,自动化并不复杂。我们尝试对自动化进行一个简单地建模,一个自动化任务(Task)通常包括这些部分:

  • 触发时机
  • 逻辑和流程
  • 目标或数据

我们平常写的代码和脚本不正是这些吗?如果将多个 Task 以一定的关系关联起来,他们具有上下游或者并行的关系,这样就成了一个流水线(Pipeline)。事实上,这正是大多数持续集成工具所提供的功能,比如 JenkinsTeamCityGoCD 等。

下图是广为使用的持续工具 Jenkins 的配置任务的界面,可以看到,其中除了一些基本信息(名字、版本…),剩下的也就是配置一下要运行的脚本:

image

下图是 GoCD 的流水线视图,流水线中有多个阶段,每个阶段又有多个步骤,每个步骤中运行若干个子任务:

image

通过这种方式,就可以将多级别、多步骤的自动化任务很好地管理起来,在合适的时机触发操作,并监控它们的运行状况。

反思对比

显然,写脚本做自动化也是需要成本的。如果一个工作重复做一遍只花 2 分钟,重复 10 遍也只花 20 分钟,那么花 5 分钟去写一个脚本是不是显得有点不值得?作为一个无害的回答可以是:你自己要去衡量它的能效比(RoI)。但我要给出的回答却是,如果一个工作你可能重复 10 遍,那么你一定接下来还会重复 20 遍、100 遍……

引用一个案例(节选自知乎用户 MetalliCat 关于当当网的回答),与诸位共勉:

08年底,亚马逊中国搞一个公关,邀请很多记者去看看他们的库房,真的是先进啊,全自动化管理,拣货员拿个扫描枪开电瓶车进去,电瓶车路线都是计算机制定好的,非常的先进。当时的当当网的库房,还是拣货员满地跑的模式,完全手工运作,热门商品堆在门口,冷门商品往里堆,看上去很落后。但是有记者同时在卓越亚马逊和当当网上下了一单,买同样的书,结果发现当当网比亚马逊还早了半天送到。

逆向追踪物流后,记者发现,当当的库房管理虽然落后,但是当当库房24小时,接两班快递,12小时接一次,亚马逊中国库房先进,24小时直接一次快递。当当网半天的优势就这么拿到的,所以当当网送货的速度反而比亚马逊中国更快一些。所以当时很多人写,当当用中国的小米步枪打败了亚马逊美式飞机大炮。

但是商业讲究的是大规模运营效率。在06年后,中国网购爆发,每年翻一番的时候,当当网的库房跟不上了,但是卓越亚马逊很轻松。导致的结果就是,10年的时候,亚马逊中国的营收是当当网的两倍,技术落后限制了当当网的发展。

回过头来,再反思一下文章开头的图文工作室里制作照片的场景,他们的工作真的高效吗?