第 1 章 软件交付的问题

第 1 章 软件交付的问题

1.1 引言

作为软件从业人员,我们面临的最重要问题就是,如果有人想到了一个好点子,我们如何以最快的速度将它交付给用户?本书将给出这个问题的答案。

我们将专注于构建、部署、测试和发布过程,因为相对于软件生产全过程的其他环节来说,这部分内容的论著较为稀少。确切地说,我们并不认为软件开发方法不重要,如果没有对软件生命周期中其他方面的关注,只把它们作为全部问题的次要因素草率对待的话,就不可能实现可靠、迅速且低风险的软件发布,无法以高效的方式将我们的劳动成果交到用户手中。

现在有很多种软件开发方法,但它们主要关注于需求管理及其对开发工作的影响。市面上也有很多优秀的书,它们详细讨论了在软件设计、开发和测试方面各种各样的方法,但它们都仅仅讲述了将软件交付给作为客户的人或组织这一完整价值流的一部分。

一旦完成了需求定义以及方案的设计、开发和测试,我们接下来做什么?我们如何协调这些活动,尽可能地使交付过程更加可靠有效呢?我们如何让开发人员、测试人员,以及构建和运维人员在一起高效地工作呢?

本书描述了软件从开发到发布这一过程的有效模式。书中讲述了帮助大家实现这种模式的技术和最佳实践,展示了它与软件交付中其他活动是如何联系的。

本书的中心模式是部署流水线。从本质上讲,部署流水线就是指一个应用程序从构建、部署、测试到发布这整个过程的自动化实现。部署流水线的实现对于每个组织都将是不同的,这取决于他们对软件发布的价值流的定义,但其背后的原则是相同的。

部署流水线的示例如图1-1所示。

图1-1 一个简单的部署流水线

部署流水线大致的工作方式如下。对于应用程序的配置、源代码、环境或数据的每个变更都会触发创建一个新流水线实例的过程。流水线的首要步骤之一就是创建二进制文件和安装包,而其余部分都是基于第一步的产物所做的一系列测试,用于证明其达到了发布质量。每通过一步测试,我都会更加相信这些二进制文件、配置信息、环境和数据所构成的特殊组合可以正常工作。如果这个产品通过了所有的测试环节,那么它就可以发布了。

部署流水线以持续集成过程为其理论基石,从本质上讲,它是采纳持续集成原理后的自然结果。

部署流水线的目标有三个。首先,它让软件构建、部署、测试和发布过程对所有人可见,促进了合作。其次,它改善了反馈,以便在整个过程中,我们能够更早地发现并解决问题。最后,它使团队能够通过一个完全自动化的过程在任意环境上部署和发布软件的任意版本。

1.2 一些常见的发布反模式

软件发布的当天往往是紧张的一天。为什么会这样呢?对于大多数项目来说,在整个过程中,发布时的风险是比较大的。

在许多软件项目中,软件发布是一个需要很多手工操作的过程。首先,由运维团队独自负责安装好该应用程序所需的操作系统环境,再把应用程序所依赖的第三方软件安装好。其次,要手工将应用程序的软件产物复制到生产主机环境,然后通过Web服务器、应用服务器或其他第三方系统的管理控制台复制或创建配置信息,再把相关的数据复制一份到环境中,最后启动应用程序。假如这是个分布式的或面向服务的应用程序,可能就需要一部分一部分地完成。

如上所述,发布当天紧张的原因应该比较清楚了:在这个过程中有太多步骤可能出错。假如其中有一步没有完美地执行,应用程序就无法正确地运行。一旦发生这种情况,我们很难一下子说清楚哪里出了错,或到底是哪一步出了错。

本书其他部分将讨论如何避免这些风险,如何减少发布当天的压力,以及如何确保每次发布的可靠性都是可预见的。

在此之前,让我们先明确到底要避免哪类失败。下面列出了与可靠的发布过程相对应的几种常见的反模式,它们在我们这个行业中屡见不鲜。

1.2.1 反模式:手工部署软件

对于现在的大多数应用程序来说,无论规模大小,其部署过程都比较复杂,而且包含很多非常灵活的部分。许多组织都使用手工方式发布软件,也就是说部署应用程序所需的步骤是独立的原子性操作,由某个人或某个小组来分别执行。每个步骤里都有一些需要人为判断的事情,因此很容易发生人为错误。即便不是这样,这些步骤的执行顺序和时机的不同也会导致结果的差异性,而这种差异性很可能给我们带来不良后果。

这种反模式的特征如下。

  • 有一份非常详尽的文档,该文档描述了执行步骤及每个步骤中易出错的地方。
  • 以手工测试来确认该应用程序是否运行正确。
  • 在发布当天开发团队频繁地接到电话,客户要求解释部署为何会出错。
  • 在发布时,常常会修正一些在发布过程中发现的问题。
  • 如果是集群环境部署,常常发现在集群中各环境的配置都不相同,比如应用服务器的连接池设置不同或文件系统有不同的目录结构等。
  • 发布过程需要较长的时间(超过几分钟)。
  • 发布结果不可预测,常常不得不回滚或遇到不可预见的问题。
  • 发布之后凌晨两点还睡眼惺忪地坐在显示器前,绞尽脑汁想着怎么让刚刚部署的应用程序能够正常工作。

相反,随着时间的推移,部署应该走向完全自动化,即对于那些负责将应用程序部署到开发环境、测试环境或生产环境的人来说,应该只需要做两件事:(1)挑选版本及需要部署的环境,(2)按一下“部署”按钮。对于套装软件的发布来说,还应该有一个创建安装程序的自动化过程。

我们将在本书中讨论很多自动化问题。当然,并不是所有的人都热衷于这个想法。那么,我们先来解释一下为什么把自动化部署看做是一个必不可少的目标。

  • 如果部署过程没有完全自动化,每次部署时都会发生错误。唯一的问题就是“该问题严重与否”而已。即便使用良好的部署测试,有些错误也很难追查。
  • 如果部署过程不是自动化的,那么它就既不可重复也不可靠,就会在调试部署错误的过程中浪费很多时间。
  • 手动部署流程不得不被写在文档里。可是文档维护是一项复杂而费时的任务,它涉及多人之间的协作,因此文档通常要么是不完整的,要么就是未及时更新的,而把一套自动化部署脚本作为文档,它就永远是最新且完整的,否则就无法进行部署工作了。
  • 自动部署本质上也是鼓励协作的,因为所有内容都在一个脚本里,一览无遗。要读懂文档通常需要读者具备一定的知识水平。然而在现实中,文档通常只是为执行部署者写的备忘录,是难以被他人理解的。
  • 以上几点引起的一个必然结果:手工部署过程依赖于部署专家。如果专家去度假或离职了,那你就有麻烦了。
  • 尽管手工部署枯燥且极具重复性,但仍需要有相当程度的专业知识。若要求专家做这些无聊、重复,但有技术要求的任务则必定会出现各种我们可以预料到的人为失误,同时失眠,酗酒这种问题也会接踵而至。然而自动化部署可以把那些成本高昂的资深高技术人员从过度工作中解放出来,让他们投身于更高价值的工作活动当中。
  • 对手工部署过程进行测试的唯一方法就是原封不动地做一次(或者几次)。这往往费时,还会造成高昂的金钱成本,而测试自动化的部署过程却是既便宜又容易。
  • 另外,还有一种说法:自动化过程不如手工过程的可审计性好。我们对这个观点感到很疑惑。对于一个手工过程来说,没人能确保其执行者会非常严格地遵循文档完成操作。只有自动化过程是完全可审核的。有什么会比一个可工作的部署脚本更容易被审核的呢?
  • 每个人都应该使用自动化部署过程,而且它应该是软件部署的唯一方式。这个准则可以确保:在需要部署时,部署脚本就能完成工作。在本书中我们会提到多个原则,而其中之一就是“使用相同的脚本将软件部署到各种环境上”。如果使用相同的脚本将软件部署到各类环境中,那么在发布当天需要向生产环境进行部署时,这个脚本已经被验证过成百上千次了。如果发布时出现任何问题的话,你可以百分百地确定是该环境的具体配置问题,而不是这个脚本的问题。

当然,手工密集型的发布工作有时也会进行得非常顺利。有没有可能是糟糕的情况刚巧都被我们撞见了呢?假如在整个软件生产过程中它还算不上一个易出错的步骤,那么为什么还总要这么严阵以待呢?为什么需要这些流程和文档呢?为什么团队在周末还要加班呢?为什么还要求大家原地待命,以防意外发生呢?

1.2.2 反模式:开发完成之后才向类生产环境部署

在这一模式下,当软件被第一次部署到类生产环境(比如试运行环境)时,就是大部分开发工作完成时,至少是开发团队认为“该软件开发完成了”。

这种模式中,经常出现下面这些情况。

  • 如果测试人员一直参与了在此之前的过程,那么他们已在开发机器上对软件进行了测试。
  • 只有在向试运行环境部署时,运维人员才第一次接触到这个新应用程序。在某些组织中,通常是由独立的运维团队负责将应用程序部署到试运行环境和生产环境。在这种工作方式下,运维人员只有在产品被发布到生产环境时才第一次见到这个软件。
  • 有可能由于类生产环境非常昂贵,所以权限控制严格,操作人员自己无权对该环境进行操作,也有可能环境没有按时准备好,甚至也可能根本没人去准备环境。
  • 开发团队将正确的安装程序、配置文件、数据库迁移脚本和部署文档一同交给那些真正执行部署任务的人员,而所有这些都没有在类生产环境或试运行环境中进行过测试。
  • 开发团队和真正执行部署任务的人员之间的协作非常少。

每当需要将软件部署到试运行环境时,都要组建一个团队来完成这项任务。有时候这个团队是一个全功能团队。然而在大型组织中,这种部署责任通常落在多个分立的团队肩上。DBA、中间件团队、Web团队,以及其他团队都会涉及应用程序最后版本的部署工作。由于部署工作中的很多步骤根本没有在试运行环境上测试过,所以常常遇到问题。比如,文档中漏掉了一些重要的步骤,文档和脚本对目标环境的版本或配置作出错误的假设,从而使部署失败。部署团队必须猜测开发团队的意图。

若不良协作使得在试运行环境上的部署工作问题重重,就会通过临时拨打电话、发电子邮件来沟通,并由开发人员做快速修复。一个严格自律的团队会将所有这类沟通纳入部署计划中,但这个过程很少有效。随着部署压力的增大,为了能够在规定的时间内完成部署,开发团队与部署团队之间这种严格定义的协作过程将被颠覆。

在执行部署过程中,我们常常发现系统设计中存在对生产环境的错误假设。例如,部署的某个应用软件是用文件系统做数据缓存的。这在开发环境中是没有什么问题的,但在集群环境中可能就不行了。解决这类问题可能要花很长时间,而且在问题解决之前,根本无法完成应用程序的部署。

一旦将应用程序部署到了试运行环境,我们常常会发现新的缺陷。遗憾的是,我们常常没有时间修复所有问题,因为最后期限马上就到了,而且项目进行到这个阶段时,推迟发布日期是不能被人接受的。所以,大多数严重缺陷被匆忙修复,而为了安全起见,项目经理会保存一份已知缺陷列表,可是当下一次发布开始时,这些缺陷的优先级还是常常被排得很低。

有的时候,情况会比这还糟。以下这些事情会使与发布相关的问题恶化。

  • 假如一个应用程序是全新开发的,那么第一次将它部署到试运行环境时,可能会非常棘手。
  • 发布周期越长,开发团队在部署前作出错误假设的时间就越长,修复这些问题的时间也就越长。
  • 交付过程被划分到开发、DBA、运维、测试等部门的那些大型组织中,各部门之间的协作成本可能会非常高,有时甚至会将发布过程拖上“地狱列车”。此时为了完成某个部署任务(更糟糕的情况是,为了解决部署过程中出现的问题),开发人员、测试人员和运维人员总是高举着问题单(不断地互发电子邮件)。
  • 开发环境与生产环境差异性越大,开发过程中所做的那些假设与现实之间的差距就越大。虽然很难量化,但我敢说,如果在Windows系统上开发软件,而最终要部署在Solaris集群上,那么你会遇到很多意想不到的事情。
  • 如果应用程序是由用户自行安装的(你可能没有太多权限来对用户的环境进行操作),或者其中的某些组件不在企业控制范围之内,此时可能需要很多额外的测试工作。

那么,我们的对策就是将测试、部署和发布活动也纳入到开发过程中,让它们成为开发流程正常的一部分。这样的话,当准备好进行系统发布时就几乎很少或不会有风险了,因为你已经在很多种环境,甚至类生产环境中重复过很多次,也就相当于测试过很多次了。而且要确保每个人都成为这个软件交付过程的一份子,无论是构建发布团队、还是开发测试人员,都应该从项目开始就一起共事。

我们是测试的狂热者,而大量使用持续集成和持续部署(不但对应用程序进行测试,而且对部署过程进行测试)正是我们所描述的方法的基石。

1.2.3 反模式:生产环境的手工配置管理

很多组织通过专门的运维团队来管理生产环境的配置。如果需要修改一些东西,比如修改数据库的连接配置或者增加应用服务器线程池中的线程数,就由这个团队登录到生产服务器上进行手工修改。如果把这样一个修改记录下来,那么就相当于是变更管理数据库中的一条记录了。

这种反模式的特征如下。

  • 多次部署到试运行环境都非常成功,但当部署到生产环境时就失败。
  • 集群中各节点的行为有所不同。例如,与其他节点相比,某个节点所承担的负载少一些,或者处理请求的时间花得多一些。
  • 运维团队需要较长时间为每次发布准备环境。
  • 系统无法回滚到之前部署的某个配置,这些配置包括操作系统、应用服务器、关系型数据库管理系统、Web服务器或其他基础设施设置。
  • 不知道从什么时候起,集群中的某些服务器所用的操作系统、第三方基础设施、依赖库的版本或补丁级别就不同了。
  • 直接修改生产环境上的配置来改变系统配置。

相反,对于测试环境、试运行环境和生产环境的所有方面,尤其是系统中的任何第三方元素的配置,都应该通过一个自动化的过程进行版本控制。

本书描述的关键实践之一就是配置管理,其责任之一就是让你能够重复地创建那些你开发的应用程序所依赖的每个基础设施。这意味着操作系统、补丁级别、操作系统配置、应用程序所依赖的其他软件及其配置、基础设施的配置等都应该处于受控状态。你应该具有重建生产环境的能力,最好是能通过自动化的方式重建生产环境。虚拟化技术在这一点上可能对你有所帮助。

你应该完全掌握生产环境中的任何信息。这意味着生产环境中的每次变更都应该被记录下来,而且做到今后可以查阅。部署失败经常是因为某个人在上次部署时为生产环境打了补丁,但却没有将这个修改记录下来。实际上,不应该允许手工改变测试环境、试运行环境和生产环境,而只允许通过自动化过程来改变这些环境。

应用软件之间通常会有一些依赖关系。我们应该很容易知道当前发布的是软件的哪个版本。

发布可能是一件令人兴奋的事情,也可能变成一件累人而又沉闷的工作。几乎在每次发布的最后都会有一些变更,比如修改数据库的登录账户或者更新所用外部服务的URL。我们应该使用某种方法来引入此类变更,以便这些变更可以被记录并测试。这里我们再次强调一下,自动化是关键。变更首先应该被提交到版本控制系统中,然后通过某个自动化过程对生产环境进行更新。

我们也应该有能力在部署出错时,通过同一个自动化过程将系统回滚到之前的版本。

1.2.4 我们能做得更好吗

当然可以,本书就是来讲如何做好这件事的。即使是在一个非常复杂的企业环境中,我们所说的这些原则、实践和技术的目标都是将软件发布工作变成一个没有任何突发事件且索然无味的事情。软件发布能够(也应该)成为一个低风险、频繁、廉价、迅速且可预见的过程。这些实践在过去的几年中已经被使用,并且我们发现它们令很多项目变得非比寻常。本书所提到的所有实践既在具有分布式团队的大型企业项目中验证过,也在小型开发组中验证过。我们确信它们是有效的,而且可以应用在大项目中。

自动化部署的威力

曾经有个客户,他们在过去每次发布时都会组建一个较大的专职团队。大家在一起工作七天(包括周末的两天)才能把应用程序部署到生产环境中。他们的发布成功率很低,要么是发现了错误,要么是在发布当天需要高度干预,且常常要在接下来的几天里修复在发布过程中引入的问题或者是配置新软件时导致的人为问题。

我们帮助客户实现了一个完善的自动构建、部署、测试和发布系统。为了让这个系统能够良好运行下去,我们还帮助他们采用了一些必要的开发实践和技术。我们看到的最后一次发布,只花了七秒钟就将应用程序部署到了生产环境中。根本没有人意识到发生了什么,只是感觉突然间多了一些新功能。假如部署失败了,无论是什么原因,我们都可以在同样短的时间里回滚。

本书的目标是描述如何使用部署流水线,将高度自动化的测试和部署以及全面的配置管理结合在一起,实现一键式软件发布。也就是说,只需要点击一下鼠标,就可以将软件部署到任何目标环境,包括开发环境、测试环境或生产环境。

接下来,我们会描述这种模式及其所需的技术,并提供一些建议帮你解决将面临的某些问题。实现这种方法,实在是磨刀不误砍柴工。

所有这些工作并不会超出项目团队的能力范围。它不需要刚性的流程、大量的文档或很多人力。我们希望,读完本章以后,你会理解这种方法背后的原则。

1.3 如何实现目标

正如我们所说,作为软件从业者,我们的目标是尽快地向用户交付有用的可工作的软件。

速度是至关重要的,因为未交付的软件就意味着机会成本。软件发布之时就是投资得到回报之时。因此,本书有两个目标,其中之一就是找到减少周期时间(cycle time)的方法。周期时间是从决定进行变更的时刻开始,包括修正缺陷或增加特性,直至用户可以使用本次变更后的结果。

快速交付也是非常重要的,因为这使你能够验证那些新开发的特性或者修复的缺陷是否真的有用。决定开发这个应用程序的(我们称为客户)会猜测哪些特性或缺陷修复对用户是有用的。然而,直到使用者真正使用之前,这些全是未经过验证的假设。这也是为什么减少周期时间并建立有效反馈环如此重要的原因。

有用性的一个重要部分是质量。我们的软件应该满足它的业务目的。质量并不等于完美,正如伏尔泰所说“追求完美是把事情做好的大敌”,但我们的目标应该一直是交付质量足够高的软件,给客户带来价值。因此,尽快地交付软件很重要,保证一定的质量是基础。

因此,我们来调整一下目标,即找到可以以一种高效、快速、可靠的方式交付高质量且有价值的软件的方法。

我们及我们的同修发现,为了达到这些目标(短周期、高质量),我们需要频繁且自动化地发布软件。为什么呢?

  • 自动化。如果构建、部署、测试和发布流程不是自动化的,那它就是不可重复的。由于软件本身、系统配置、环境以及发布过程的不同,每次做完这些活动以后,其结果可能都会有所不同。由于每个步骤都是手工操作,所以出错的机会很大,而且无法确切地知道具体都做了什么。这意味着整个发布过程无法得到应有的控制来确保高质量。常常说软件发布像是一种艺术,但事实上,它应该是一种工程学科。
  • 频繁做。如果能够做到频繁发布,每个发布版本之间的差异会很小。这会大大减少与发布相关的风险,且更容易回滚。频繁发布也会加快反馈速度,而客户也需要它。本书很多内容都聚焦于如何尽快得到对软件及其相关配置所做变化的反馈,这包括其环境、部署过程及数据等。

对于频繁地自动化发布来说,反馈是至关重要的。下面关于反馈的三个标准是很有用的:

  • 无论什么样的修改都应该触发反馈流程;
  • 反馈应该尽快发出;
  • 交付团队必须接收反馈,并依据它作出相应的行动。

让我们逐一审视一下这三个标准,考虑如何能达到这样的标准。

1.3.1 每次修改都应该触发反馈流程

一个可工作的软件可分成以下几个部分:可执行的代码、配置信息、运行环境和数据。如果其中任何一部分发生了变化,都可能导致软件的行为发生变化。所以我们要能够控制这四部分,并确保任何修改都会被验证。

当修改了源代码后,可执行代码当然也就会随之发生变化。因此每当修改源代码后,都要进行构建和测试。为了能够控制这个流程,构建可执行代码并对其进行测试都应该是自动化的。每次提交都对应用程序进行构建并测试,这称作持续集成。我们会在第3章详细描述它。

之后的部署活动中都应该使用这个构建并测试后的可执行代码,无论是部署至测试环境,还是生产环境。如果你的应用软件需要编译,你应该确保在所有需要可执行代码的地方都使用在构建流程中已生成的这个,而不是再重新编译一次生成一个新的。

对环境的任何修改都应该作为配置信息来管理。无论在什么环境下,对于应用程序配置的变更都应该被测试。如果用户自己安装软件的话,任何可能的配置项都应该在各种具有代表性的环境上测试。1 配置管理将在第2章中讨论。

1对于跨平台的通用软件,应该在不同的操作系统,甚至同一操作系统的不同版本上进行测试。——译者注

如果需要修改该应用程序所要被部署的运行环境,那么整个系统都应该在修改后的环境中进行测试。这包括对操作系统配置、该应用程序所依赖的软件集、网络配置,以及任何基础设施和外部系统的修改。第11章会讲基础设施和环境的管理,包括自动化地创建及维护测试环境和生产环境。

如果是数据结构发生了变化,这些变化也同样要经过测试。我们在第12章讨论数据管理。

什么是反馈流程?它是指完全以自动化方式尽可能地测试每一次变更。根据系统的不同,测试会有所不同,但通常至少包括下面的检测。

  • 创建可执行代码的流程必须是能奏效的。这用于验证源代码是否符合语法。
  • 软件的单元测试必须是成功的。这可以检查应用程序的行为是否与期望相同。
  • 软件应该满足一定的质量标准,比如测试覆盖率以及其他与技术相关的度量项。
  • 软件的功能验收测试必须是成功的。这可以检查应用是否满足业务验收条件,交付了所期望的业务价值。
  • 软件的非功能测试必须是成功的。这可以检查应用程序是否满足用户对性能、有效性、安全性等方面的要求。
  • 软件必须通过了探索性测试,并给客户以及部分用户做过演示。这些通常在一个手工测试环境上完成。此时,产品负责人可能认为软件功能还有缺失,我们自己也可能发现需要修复的缺陷,还要为其写自动化测试来避免功能退化。

运行测试的这些环境应该尽可能与生产环境相似,从而验证对于环境的任何修改都不会影响应用程序的正常运行。

1.3.2 必须尽快接收反馈

快速反馈的关键是自动化。对于实现完全自动化过程来说,唯一的约束条件就是你能够使用的硬件数量。如果是手工过程,我们可以通过人力来完成这个工作。然而,手工操作会花更长的时间,可能引入更多的错误,并且无法审计。另外,持续做手工构建、测试和部署非常枯燥而且有重复劳动,与人力资源利用率的准则相悖。人力资源是昂贵且非常有价值的,所以我们应该集中人力来生产用户所需要的新功能,尽可能快速地交付这些新功能,而不是做枯燥且易出错的工作。像回归测试、虚拟机的创建和部署这类工作最好都由机器来完成。

当然,实现这样的部署流水线是需要大量资源的,尤其是当有了全面的自动化测试套件之后。部署流水线的关键目的之一就是对人力资源利用率的优化:我们希望将人力释放出来做更有价值的工作,将那些重复性的体力活交给机器来做。

对于整个流水线中的提交(commit)阶段,其测试应具有如下特征。

  • 运行速度快。
  • 尽可能全面,即75%左右的代码库覆盖率。只有这样,这些测试通过以后,我们才对自己写的软件比较有信心。
  • 如果有测试失败的话,就表明应用程序有严重问题,无论如何都不能发布。也就是说,像检查界面元素的颜色是否正确这类测试不应该包含在这个测试集合当中。
  • 尽可能做到环境中立。这个环境没必要和生产环境一模一样,可以相对简单廉价一些。

相对而言,提交阶段之后的测试一般有如下这些特点。

  • 它们通常运行更慢一些,所以适合于并行执行。
  • 即使某些测试有可能失败,但在某种场合下,我们还是会发布应用程序。比如某个即将发布的版本有一个不稳定的修复,会导致其性能低于预先定义的标准,但有时我们还是会决定发布这个版本。
  • 它们的运行环境应该尽可能与生产环境相同。除了测试功能以外,它同时还会对部署过程以及对生产环境的任何修改进行测试。

先经过一轮测试(在便宜的硬件上运行最快的那些测试)之后,再经过这种测试过程,会让我们对软件更有信心。如果这些测试失败了,这个构建版本就不会再进入后续阶段,这样就可以更好地利用资源。第5章中会详细介绍流水线技术,而第7、8、9章中会分别讲述提交测试阶段、自动化验收测试,以及非功能需求的测试。

这种方法的基础之一就是快速的反馈。为了确保对变更的快速反馈,我们就要注意开发软件的流程,特别是如何使用版本控制系统和如何组织代码。开发人员应该频繁提交代码到版本控制系统中,像管理大规模团队或分布式团队那样,将代码分成多个组件。在大多数情况下,应该避免使用分支。我们将在第13章讨论增量式交付以及组件的使用,在第14章中讨论分支与合并。

1.3.3 交付团队必须接收反馈并作出反应

参与软件交付过程的所有人(包括开发人员、测试人员和运维人员、数据库管理员、基础设施的专家以及管理者)都应该参与到这个反馈流程中,这是至关重要的。如果这些人无法做到每天都在一起工作(尽管我们认为团队应该是全功能团队),就一定要常常碰头并一起探讨如何改进软件交付的流程。对于快速交付高质量的软件来说,基于持续改进的过程是非常关键的。迭代过程有助于为这类活动建立规律性,例如每个迭代至少开一次回顾会议,在会上每个人都应参与讨论如何在下一个迭代中改进交付过程。

想要能够根据反馈来调整行动,就要对信息进行广播。使用一个大且可视的仪表盘(并非一定要电子的),或者其他通知机制对于确保反馈送达到每一个人是极为重要的。这个仪表盘应该随处可见,而且至少每个团队的屋中都应放置一个。

当然,如果最后没有引发什么改进行动,反馈也就没有什么用了。因此,这就要求纪律性和计划性。当需要采取行动时,整个团队有责任停下他们手中的事情,来决定接下来采取哪些行动。在完成此事之后,团队才能继续自己的工作。

1.3.4 这个流程可以推广吗

很多人认为我们所描述的过程太理想化了。他们认为,这样的事情在小团队中可能行得通,但在大型分布式项目中是不行的!

我们在过去的几年中,在不同的行业里做过很多大型项目。我们非常幸运,曾与有各种不同经验的同事在一起工作。本书中描写的所有技术与实践,在各种组织(无论大型组织还是小型组织)各种情况下的真实项目中都被证明是有效的。也正是在此类项目中一次又一次经历同样的问题,才促使我们写了这本书。

你会注意到,书中的很多东西都来自于精益思想和哲学。精益制造的目标是确保快速地交付高质量的产品,它聚焦于消除浪费,减少成本。多个行业的实践已经证明,精益制造可以节省大量成本和资源,带来高质量的产品,缩短产品上市时间。这一哲学在软件开发领域也渐成主流,而且影响着本书中的很多内容。精益并不仅仅局限于应用在小系统上,它已在大型组织甚至整个经济体系中得到应用。

根据我们的经验,这一理论与实践既可以应用在大型组织中,也可以应用于小团队,但我们并不要求你马上相信我们所说的。自己试一试,就能找到答案。保留那些对你的团队有效的实践,放弃那些无效的实践,将你的经验写下来,以便别人可以借鉴。

1.4 收效

对于前面我们讲到的这种方法,其主要收益是创建了一个发布流程,这个流程是可重复的、可靠的且可预见的,从而大大缩短了发布周期,使新增功能和缺陷修复能更早与用户见面。节省下来的成本将不仅仅是金钱,还包括建立和维护这样一个发布系统所需要的时间投入。

除此之外,还有其他一些收益,虽然其中一些我们应该能够预见到,但另一些很可能是我们无法预测的,但一旦被发现,可以给我们带来惊喜。

1.4.1 授权团队

部署流水线的一个关键点是,它是一个“拉动”(pull)系统,它使测试人员、运维人员或支持服务人员能够做到自服务,即他们可以自行决定将哪个版本的应用程序部署到哪个环境中。根据我们的经验,对缩短发布周期的主要贡献者是那些在整个交付流程中,等待拿到应用程序的某个“好”版本的人。为了得到一个可用的版本,通常需要很多的电子邮件沟通、问题跟踪单,以及其他效率不高的沟通方式。假如是分布式交付团队的话,这一点就成了主要的低效之源。然而实现了部署流水线之后,这个问题就彻底解决了,因为每个人都能看到应该拿哪个版本部署到相应的环境中,而且只需要单击按钮就能完成部署。

我们常常看到在不同的环境中运行着不同的版本,而不同角色的人工作在其上。能够轻松地将任意版本的软件部署到任意环境的能力能带来很多好处。

  • 测试人员可以选择性地部署较旧的版本,以验证新版本上的功能变化。
  • 技术支持人员可以自己部署某个已发布的版本,用于重现缺陷。
  • 作为灾难恢复手段,运维人员可以自己选一个已知的正确版本,将其部署到生产环境中。
  • 发布方式也变成一键式的了。

我们的部署工具为他们提供的灵活性,改变了他们的工作方式,能让其变得更美好。总而言之,团队成员可以更好地控制工作节奏,从而改进工作质量,这就会让应用程序的质量得以提高。他们之间的协作更加有效,无用的交互更少,可以更高效地工作,因为不需要花太多的时间等待可用的版本。

1.4.2 减少错误

我们可能从方方面面将错误引入到软件中。最初委托制作这个软件的人就可能出错,比如提出错误的需求。需求分析人员可能将需求理解错了,开发人员也可能写出了到处都是缺陷的程序,而我们在这里要说的错误是指由不良好的配置管理引入到生产环境的错误。我们将在第2章详细阐述配置管理。现在,让我们想一下到底需要哪些东西才可以让一个应用程序正确地工作,当然肯定需要正确版本的代码,除此之外呢?我们还需要数据库模式(schema)的正确版本、负载均衡器的正确配置信息、应用程序所依赖的Web服务(比如用于查阅价格的Web服务)的正确URL等。当我们说配置管理时,指的是让你识别并控制一组完整信息的流程与机制,这些信息包括每个字节和比特。

一个比特能有多大的影响

几年前,Dave2为一个知名零售商开发了一个大型销售系统。当时还是我们考虑自动化部署的初期。因此,有些东西被很好地自动化了,而有些则没有。有一次,生产环境中出现了一个非常难修复的缺陷。我们在日志中突然发现很多从来没见过的异常记录,很难诊断是由于什么原因造成的,而且在所有的测试环境中都不能重现这个问题。我们尝试了很多方法,比如在性能环境中进行负载测试,试图模拟生产环境中可能的不合理情况,但最终还是无功而返。之后,我们还做了很多研究分析(当然不止我在这里描述的)。我们最终决定,调查每一个我们认为在两个系统(生产环境和测试环境)中有可能引起行为差异的东西。最后发现,我们的应用程序所依赖的一个二进制库(它属于我们所用的应用服务器软件的一部分),在生产环境和测试环境中是不同的版本。我们修改了生产环境中二进制库文件的版本,问题就解决了。

这个故事并不是想说明我们工作不够勤勉或小心,或者我们非常聪明能想到检查系统。其关键在于说明软件真的非常脆弱。这是一个相当庞大的系统,有数万个类,几千个库,以及很多的外部集成点。然而这个严重的问题却是由某个第三方二进制文件不同版本间几个字节的不同引入到生产环境中的。

2本书正文将沿用David的昵称Dave。——编者注

现在的软件系统常常是由几GB的内容组成,没有哪个团队或个人能够在没有机器帮助的情况下,轻松地查出这种大规模软件中的一小处不同。与其等到问题发生,为什么不利用机器的辅助作用在第一时间防止它发生呢?

通过积极地管理在版本控制库中的所有可能变动的内容,比如配置文件、创建数据库及其模式的脚本、构建脚本、测试用具,甚至开发环境和操作系统的配置,我们让计算机来做它们擅长的所有事情,即确保所用的比特和字节都在它们应该在的位置上,至少确保在代码将要运行时确实如此。

手工配置管理的成本

我们曾开发的另一个项目有大量专门的测试环境。每个专门的测试环境运行一个普通的EJB应用程序服务器。此项目是作为敏捷项目开发的,具有良好的自动化测试覆盖率。本地构建得到了很好的管理,开发人员可以很容易地让代码在本地运行,方便开发。然而,这是我们在更仔细考虑应用程序的部署自动化问题之前。那时,我们的每个测试环境都是通过应用服务器供应商基于控制台的工具进行手工配置的。虽然开发人员用于自己本地安装的配置文件副本都被施加了版本控制,但是对每个测试环境的配置却没有做到这一点。而且这些测试环境之间都有所不同。它们配置属性的顺序不同,有些属性丢失了,有些属性的值不相同,还有一些属性的名字不同,而某些属性是针对某个特定环境的,在其他环境中无效。根本找不到两个完全一样的测试环境,而且所有的测试环境都和生产环境不一样。这使我们极难确定哪些属性是必需的,哪些是多余的,哪些应该是环境共有的,哪些是某种环境特有的。结果,这个项目需要一个五人团队来专门负责管理这些不同环境的配置。

根据我们的经验,这种依赖手工的配置管理很常见。在我们所参与项目的很多客户组织中,这些都是在生产环境和测试环境中实际发生的事情。一般来说,服务器A的连接池限数为100,而B的限数是120,这类问题通常并不打紧,但某些时候却是至关重要的。

你绝对不想在业务交易最忙的时段里有突发事故,更不想发现它是由于配置项的不一致性导致的。这种情况通常发生在那些用于指定软件运行环境的配置项上,而且这种配置信息实际上经常通过代码指定新的执行路径。我们必须考虑到这类配置信息的更改,并且需要像对待代码一样,对代码运行的环境进行良好的定义与控制。假如我们能够接触到你的数据库配置、应用服务器或Web服务器的话,肯定可以让你的应用程序更快出故障,而且比直接修改你的编译器或源代码来得更快更容易。

假如这类配置参数都是由手工配置和管理的话,难免会在那种重复性的工作中出现人为错误。在一些重要的位置上,只要一个简单的输入失误就可以让应用程序停止运行。编程语言可以通过语法检查来发现编译问题,单元测试可以验证代码中有没有输入错误。可是很少有哪种检查方式可以用于配置信息的验证,尤其是当这些配置信息是在某个控制台上直接输入的时候。

所以,请将配置信息放在版本控制系统中。这个最简单的动作就是一个巨大的进步。至少当你不小心修改了配置信息,版本控制系统会提醒你。这就至少消除了一种非常常见的错误源。

当所有的配置信息都放在版本控制系统中以后,接下来就要消除“中间人”了,即让计算机直接使用这些配置信息,而不是再通过手工输入的方式来进行软件配置。虽然某些技术相对来说更顺应这种方式,但是当你(通常是基础设施提供商)仔细思考一下如何管理这类配置信息,尤其是那些最难驾驭的第三方系统的配置信息时,会惊奇地发现还有很长的路要走。我们将在第4章详细讨论相关内容,而更深入的讨论将在第11章进行。

1.4.3 缓解压力

明显的好处中,可以缓解压力是最吸引所有与发布相关的人的一点。绝大多数经历过项目发布的人都会认为,当项目越临近发布日期,就越能感觉到压力。根据我们的经验,压力本身就是问题的根源所在。一些敏感、保守且具有质量意识的项目经理常常对开发人员说:“都这个时候了,你就不能直接修改一下代码吗?”或者让数据库管理员把他们并不清楚来路的数据录入到应用程序的数据库表中。像以上两种情况,或者其他许多类似的情况下,其压力是通过传达“只要让它可以工作就行了”这一信息表现出来的。

不要误解,我们也遇到过同样的事情。我们并不是说这种处理方式一定是错误的,如果刚把代码部署到了生产环境中,而你的组织因为它的某个缺陷遭受经济上的损害时,任何阻止这种损害的行为都是可以理解的。

我们的不同观点在于,上面所提到的为了让刚部署的产品环境可以正常运行的这两种快速修补并不一定是业务需要使然,而更多的可能是由于“今天是计划已久的发布日期”所带来的压力导致的。这里的问题在于系统上线是一个非常重大的事件。只要这是事实,就会有很多的庆典和紧张气氛。

现在,让我们来设想一下。如果接下来的发布只需要单击一下按钮,而且只需要等上几分钟,甚至几秒钟内就可以完成。另外,假如发生了非常糟糕的事情,你只要花上相同的几分钟或几秒钟的时间就可以把刚部署的内容恢复到从前的老样子。再大胆地设想一下,假如你的软件发布周期总是很短,那么当前生产环境中的版本与新版本之间的差异应该非常小。如果上述设想都是事实的话,那么发布的风险一定会大大降低,那种将职业生涯压注在发布是否成功的不爽感觉也将大大减少。

对于很少的一部分项目来说,这种理想状态可能很难成为现实。然而,对于大多数项目来说,尽管可能需要花上一些精力,但肯定是可以做到的。减少压力的关键在于拥有一个我们前面所描述的自动化部署过程,并频繁地运行它,当部署失败后还能够快速恢复到原来状态。尽管刚开始做自动化时可能会很痛苦,但它会渐渐地变得容易起来,而它给项目和团队带来的好处是不可限量的。

1.4.4 部署的灵活性

在一个全新环境上运行应用程序应该是相当简单的事。理想情况下,只要安装机器或虚拟镜像,然后配置一些与具体运行环境相关的特定选项。然后,你就可以使用自动化过程准备好新的部署环境,并选择指定的应用程序版本进行部署。

在笔记本电脑上运行企业级软件

我们最近做过一个项目,该项目就是根据新的业务要求创建一个企业核心系统。该业务涉及跨国事务,软件需要部署在不同类型的昂贵的计算机上。可是,由于政府法规的突然变化,客户业务流程也不得不作出相应的调整。项目可能会被取消的消息自然让每个人都有点失望。

但对于我们来说,还有一点儿希望。聘请我们开发软件的客户做了一个小规模分析。“这个系统的最小硬件配置是什么?我们如何节省资金成本?”他们问道。“让它在笔记本电脑上运行”,我们回答道。他们非常吃惊,因为这可是一个非常复杂的多用户系统。“你们如何确保它的可行性?”他们仔细想了想后,还是担心地问道。“我们可以使用这种方式来运行所有的验收测试,……”然后,我们给客户做了演示。“它的负载需求是什么?”我们问道。他们把负载需求告诉了我们,而我们只修改了一行代码,增加了几个参数,就可以在笔记本电脑上做性能测试了。尽管在笔记本电脑上运行的确比较慢,但并不是慢得离谱。只要一个配置稍好一点儿的服务器就可以满足他们的需求,而事实证明,在这样的服务器上只需要几分钟就可以让应用程序运行起来。

当然,这种部署上的灵活性不只是由于我们本书所讲的这种自动化部署技术,良好的应用程序架构设计也很好地支持了这种方式。然而,这种“只要需要,就可以让软件运行在任何环境中”的能力使我们和客户对我们随时管理所有版本发布过程充满信心。这样一来,发布变得不再让大家那么焦虑,正如敏捷所强调的,在每个迭代结束时进行发布这件事就变得很容易了。尽管并不一定每个项目都能够完全做到这种程度,但这会让我们享受属于自己的周末时光。

1.4.5 多加练习,使其完美

在所参与的项目中,我们都会设法让每个开发人员都拥有自己的专属开发环境。但是,即使在那些做不到这一点的项目中,使用持续集成或迭代增量开发的团队也要频繁地部署应用程序。

最好的策略就是无论部署到什么样的目标环境,都使用相同的部署方法。不应该有特殊的QA部署策略,或者一个特殊的验收测试或生产部署策略。在每次以同一种方式部署应用软件时,也是验证我们的部署机制是否正确的时机。事实上,向其他任何环境的任何一次部署过程都是生产环境部署的一次演练。

只有一种环境可以有多变性,那就是开发环境。开发人员应该在自己的开发环境中自行生成二进制文件,而不需要在别处构建生成。所以,对这种开发环境的部署流程要求太严格是没有必要的。虽然我们能够做到在开发人员的开发机器上也以同样的方式部署软件,但实际上对开发环境的部署没有必要严格要求。

1.5 候选发布版本

什么是候选发布版本(release candidate)?对于代码的任何一次修改都有被发布出去的可能性。当你问自己“我们这次修改的版本是否应该发布出去”时,得到的答案很可能只是一次臆测的结果而已。然而,恰恰是构建、部署、测试流程能够验证是否可以发布这次修改后的版本。对于“是否可以发布这次修改的版本”这个问题,这一流程会不断让我们增强信心。我们只做很小的修改(无论是新功能,还是修复缺陷,或是提高一些性能),并且验证我们是否有足够高的自信把这个带有本次修改的系统发布出去。为了进一步减少发布风险,我们希望尽可能在最短的时间内完成这个验证过程。

尽管每次修改都可以产生一个能够交给用户的最终产物,但是我们应该首先对每次修改都进行适用性评估。只有这次修改没有缺陷,而且满足由客户定制的验收条件,才能够发布它。

大多数软件发布方法都是在其流程的最后阶段才能识别出可以发布的那些版本。当说到与跟踪(tracking)相关的工作时,这是有些意义的。在写作本书时,Wikipedia上对开发阶段的描述中将“候选发布版本”作为这一流程中的一个步骤进行了说明,如图1-2所示。我们的观点则稍有不同。

图1-2 对于发布候选版本的传统观点

在传统软件开发方法中,通常以较长时间的验证过程来确保软件满足质量要求并实现了全部功能需求,之后才确定能够发布的候选版本。然而,当有全面的自动化测试,并且构建和部署也是自动化过程时,我们在项目后期就不再需要冗长且手工密集型的测试了。在这一阶段应用程序的质量通常也会比较高,手工测试只是用于证实功能完备就行了。

根据我们的经验,直到开发阶段之后才做测试的话,无疑会降低应用程序的质量。最好还是在缺陷被引入时,就发现并将其解决。发现得越晚,修复的成本越高。开发人员已经不记得他们是在实现哪个功能时把缺陷引入的,而这个功能很可能已经发生了变化。直到最后才做测试,这通常意味着没有足够的时间真正地修复缺陷,或者只能修复其中很少的一部分缺陷。因此,我们想尽早地发现并修正这些缺陷,最好是在将其提交到代码库之前。

每次提交代码都可能产生一个可发布的版本

开发人员对代码库的每次修改都应该是以某种方式为系统增加价值。每次代码到版本控制系统的提交都应该是对当前所开发软件的提高或增强。我们如何知道它的正确性呢?唯一的方法就是运行这个软件,看它的行为是否符合我们的期望。大多数项目都将这部分工作推迟到了开发的后期。这就意味着,即使它不能工作,也只有当有人测试或使用这个软件时才能被发现,而此时的修复成本通常会比较高。这个阶段通常称作集成阶段,常常是整个开发过程中最不可预测、最不易管理的阶段。由于集成这件事太痛苦了,所以团队总是推迟集成工作。然而,集成频率越低,集成时我们就会越痛苦。

如果在软件开发中的某个任务令你非常痛苦,那么解决痛苦的方法只有更频繁地去做,而不是回避。因此,我们应该频繁做集成,事实上应该在每次提交修改后都做集成。持续集成这个实践将频繁集成发挥到了极至,而“持续集成”转变了软件开发过程。持续集成会及时检测到任何一次破坏已有系统或者不满足客户验收测试的提交。一旦发生这种情况,团队就立刻去修复问题(这是持续集成的首要规则)。如果能够坚持这个实践,那么软件会一直处于可用状态。假如测试足够全面,且运行测试的环境与生产环境足够相近(甚至相同)的话,那么可以说,你的软件一直处于可发布状态。

我们可以把每次修改都作为一个有可能被发布的候选版本。每次将修改后的代码提交到版本控制系统时,我们都希望它能够通过所有的测试,产生可工作的软件,并能够发布到生产环境中,而这只是我们的一个假设。持续集成系统的职责就是推翻这一假设,证明某个版本并不适合部署到生产环境中。

1.6 软件交付的原则

本书所阐述的思想理念已经被作者在过去多年中经历的项目所证明。随着不断地总结,并把它们记录在这里,我们发现同样的原则一次又一次的出现。我们在这里列举一下。如果说我们之前提到的某些方法可能还需要进一步解释或者谨慎使用的话,那么下面这些原则就完全没有这个必要了。没有以下这些事情做支撑,根本无法想象我们会有一个高效的交付流程。

1.6.1 为软件的发布创建一个可重复且可靠的过程

这个原则是我们写这本书的一个目标:让软件发布成为一件非常容易的事情。事实上,它的确应该是件很容易的事,因为在发布之前,对发布流程中的每一个环节,你都已经测试过数百次了。它就应该像单击一个按钮那么容易。这种可重复性和可靠性来自于以下两个原则:(1)几乎将所有事情自动化;(2)将构建、部署、测试和发布软件所需的东西全部纳入到版本控制管理之中。

归根结底,软件部署包括三件事:

  • 提供并管理你的软件所需要的运行环境,这包括硬件配置、所依赖的软件、基础设施以及所需的外部服务;
  • 将你的应用程序的正确版本安装在其之上;
  • 配置你的应用程序,包括它所需要的任何数据以及状态。

对于应用程序的部署,应该由版本控制系统中的全自动化过程来完成。通过保存在版本控制系统或数据库中的必要脚本和状态信息,应用程序的配置也可以是一个全自动化过程。显然,硬件是无法纳入版本控制的,但利用廉价的虚拟化技术和像Puppet这样的工具,这类支撑过程也可以全部自动化。

本书的后续内容将详细讲述实现这一原则的策略。

1.6.2 将几乎所有事情自动化

有些工作是不可能被自动化的。比如,探索性测试就依赖于有经验的测试人员。向用户代表们演示程序也无法利用计算机来自动完成。人工的审批流程也需要人的干预。但是,这类不能被自动化的事情要比人们想象的要少很多。通常,在需要人做决定的那一时刻之前,构建流程应该是完全自动化的。对于部署流程也是一样。也就是说,整个软件发布流程都适用这一原则。验收测试是可以自动化的,数据库的升级和降级也是可以自动化的,甚至网络和防火墙配置也是可以自动化的。你应该尽可能自动化所有的东西。

有人可能会说,如果有足够的能力和时间,就可以将任何构建或部署流程自动化。

大多数开发团队都没有将发布流程自动化,因为看上去自动化发布流程是一个令人怯步的工作,而手工完成这些事情显得更容易一些。如果我们只需要做一次这样的工作,通过手工执行的确非常容易,但如果需要执行这个流程数十次的话,就不是那么容易的事了,而且很可能在第三次或第四次的时候就感觉不那么容易了。

自动化是部署流水线的前提条件。因为只有通过自动化,才能让大家仅通过单击一下按钮就得到他们所想要的。当然,你不需要把所有的东西一次性地全部自动化。你应该看一下在构建、部署、测试和发布过程中,哪个环节是瓶颈。随着时间的推移,最终你可以,也应该将所有环节全部自动化。

1.6.3 把所有的东西都纳入版本控制

将构建、部署、测试和发布的整个过程中所需的东西全部保存在某种形式的版本存储库中,包括需求文档、测试脚本、自动化测试用例、网络配置脚本、部署脚本、数据库创建、升级、回滚和初始化脚本、应用程序所依赖的软件集合的配置脚本、库文件、工具链以及技术文档等。所有这些内容都应该受到版本控制,与每次构建结果相关的版本都应可以识别。也就是说,这些变更集(change set)都应该有唯一标识,比如构建号、版本控制库中的版本号。

一个刚刚加入团队的新成员应该可以坐在一台新分配给他的开发电脑前,直接从项目的版本库中签出代码,并只需要运行一条命令就能构建应用程序,并将其部署到任意一个允许的环境中,包括本地开发机器。

另外,应该也能够方便地知道当前每个环境中到底部署了应用程序的哪个版本,及其在版本库中所对应的版本号。

1.6.4 提前并频繁地做让你感到痛苦的事

这是最通用的原则,也是最有启发性的。在软件交付这个领域,它可能是最有用的一个启发式原则,我们所说的一切都可以归结到这一点上。集成通常是一个非常痛苦的过程。如果你的项目也是如此,那么就应该在每次有人提交代码后立刻进行集成,而且应该从项目一开始就这么做。如果测试是发布之前最痛苦的事情,那么就别拖到最后,而是应从项目一开始就不断地进行测试。

如果软件发布很痛苦的话,就尝试在每次代码提交并通过所有自动化测试之后就进行发布。如果无法做到每次提交代码后就发布给真正的用户,那么每次提交后可以将其发布到类生产环境中。如果创建应用程序的说明文档是你的痛点,那么每开发一个功能时就应写好文档,而不是留到最后一起写。把一个功能的说明文档也作为“DONE”的一个验收条件,并尽可能自动化这个过程。

根据你当前的专业技术知识水平,要想做到这一点很可能会花很多功夫,但你又无法和客户说:“因为我要做自动化,所以就不能交付新功能了。”所以,你可能需要选择一个中期目标,比如每隔几周做一次内部发布。假如你现在就是这么做的,那么就每周做一次。逐步地走向理想状态,即使是一小步一小步地进行,也会带来很大的价值。

极限编程就是把这一启发式原则应用到软件开发后的一个结果。本书中的很多建议都来自于将这一原则应用于软件交付过程的经验总结。

1.6.5 内建质量

这一原则和上一原则(持续改进)都是从精益运动(lean movement)中借鉴来的。“内建质量”也是戴明(精益运动的先驱之一)提出的名言之一。越早发现缺陷,修复它们的成本越低。如果在没有提交代码到版本控制之前,我们就能发现并修复缺陷的话,代价是最小的。

本书中所描述的一些技术,比如持续集成、全面的自动化测试和自动化部署都是为了在这个交付流程中尽早地发现问题(“提前做麻烦的事”在现实中的应用之一),然后修复它们。假如每个人都对火警信号听而不闻,视而不见的话,火警信号就没有意义了。因此,交付团队必须执行铁一般的纪律:一旦发现缺陷,就要马上着手修复。

“内建质量”还有另外两个推论。(1)测试不是一个阶段,当然也不应该开发结束之后才开始。如果把测试留在最后,那就为时晚矣,因为可能根本没有时间修复那些刚被发现的问题。(2)测试也不纯粹或主要是测试人员的领域。交付团队的每个人都应该对应用程序的质量负责。

1.6.6 “DONE”意味着“已发布”

你是否经常听到某位开发人员说“这个用户故事(或功能)已经完成了”?也许你还经常听到项目经理问这位开发人员“它真的完成了吗”?那么“DONE”到底是什么意思呢?实际上,我们认为,一个特性只有交到用户手中才能算“DONE”。这是持续部署实践背后的动机之一(参见第10章)。

对于一些敏捷交付团队来说,“DONE”意味着软件已经部署到生产环境上。对于软件项目来说,这是一种理想状态。将其作为衡量是否完成的标准,并不总是合适的。对于那些第一次发布的软件系统来说,它可能需要一段时间才能达到“让外部用户真正从该软件身上获益”的状态。因此,我们可以暂且退让一步,只要某个功能在类生产环境上向客户代表做过演示,并且客户代表试用之后就认为是完成了。

根本没有“已经完成了80%”这一说法。任何事情要么是完成了,要么就是没完成。我们可以估计尚未完成的某件工作还需要多少工作量,但仅仅是估计而已。当事实证明那些还剩余百分之几的估计不正确(事实总是如此)时,估计剩余工作总量的做法总是备受指责。

这一原则有个很有趣的推论:一件事情的完成与否,并不是一个人能控制得了的,它需要整个交付团队共同来完成。这就是为什么所有人(包括开发、测试、构建和运维人员和技术支持人员)在项目一开始就应该在一起工作。这也是为什么整个交付团队应该对交付负责。这个原则非常重要,所以我们接下来要用专门的一节来讨论它。

1.6.7 交付过程是每个成员的责任

理想情况下,团队中的成员应该有共同的目标,并且每个成员应在工作中互相帮助来实现这一目标。无论成功还是失败,其结果都属于这个团队,而非个人。可是,现实是很多项目都是开发者开发后将困难转交给测试者,而测试者又在发布时将困难转嫁到运维团队。当出现问题时,人们花费大量的时间来修复错误,并用同等的时间来互相指责。其实,这些错误是这种各自为政的工作方式所不可避免的结果。

假如你工作于小规模团队或相对独立的部门,也许对发布软件所需的资源有绝对的控制能力。如果是这样,当然非常好啦。假如不是这样的话,你就要有思想准备,很可能需要长期的艰苦工作才能打破不同角色之间的壁垒。

从一个新项目的开始就要保证团队成员能够一起参与到发布程序的过程当中,以保证他们有机会频繁且有规律地进行交流。一旦障碍消失,交流就应持续进行,但我们可能需要逐步地向目标迈进。比如建立一个系统,在这个系统上,每个人都可以一眼就知道应用程序所处的状态,比如其健康状况、各种构建版本、构建通过了哪些测试、它们可被部署到的环境的状态。这个系统应该能让大家执行完成作业的动作,比如向某个环境中部署软件。

这是DevOps运动的核心原则之一。DevOps运动的焦点和我们这本书的目标一致:为了更加快速且可靠地交付有价值的软件,鼓励所有参与软件交付整个过程中的人进行更好的协作。[aNgvoV]

1.6.8 持续改进

这里我们要强调的是:应用程序的首次发布只是其生命周期中的第一个阶段。随着应用程序的演进,更多的发布将会接踵而来。更重要的是,你的交付过程应该随之不断演进。

在交付过程中,整个团队应该定期地坐在一起,召开回顾会议,反思一下在过去一段时间里哪些方面做得比较好,应该继续保持,哪些方面做得不太好,需要改进,并讨论一下如何改进。每个改进点都应该有一个人负责跟踪,确保相应的改进活动能够被执行。当下一次团队坐在一起时,他们应该向大家汇报这些活动的结果。这就是众所周知的戴明环:计划-执行-检查-处理(PDCA)

关键在于组织中的每个人都要参与到这个过程当中。如果只在自己所在角色的内部进行反馈环,而不是在整个团队范围内进行的话,就必将产生一种“顽疾”:以整体优化为代价的局部优化,最终导致互相指责。

1.7 小结

传统上,软件发布过程充满压力。而且与我们对代码的创建和管理相比,软件发布过程更像是一个缺乏验证的手工过程,它的系统配置的关键部分都依赖于临时性的配置管理方法。在我们看来,这种软件发布的压力与发布过程中的手工且易错的特质是密不可分的。

通过采用自动构建、测试和部署技术,可以获得很多益处,我们将能够验证变化,重现各种环境中的部署过程,在很大程度上减少产品出错的机会。由于发布过程本身已不再是一个障碍,我们可以部署软件变更,从而更快地获得商业利益。实施自动化系统会促使我们将好的实践付诸行动,比如行为驱动的开发(behavior-driven development)和综合的配置管理等。

我们还能与家人和朋友共度周末,享受没有压力的生活,而工作也会变得更加高效。为什么不呢?生命如此短暂,我们不能把自己的假期浪费在计算机旁,做那些枯燥无味的部署工作。

自动化的开发、测试以及发布过程对发布软件的速度、质量和成本有着深远的影响。作为作者的我们就有一人曾从事与一个非常复杂的分布式系统有关的工作。将软件发布到生产环境的过程(包括大型数据库的数据迁移)只需要花费5~20分钟,这取决于与某次发布相关的数据迁移的规模。其中移动数据会用掉很长时间。一个与该系统密切相关且可相比拟的项目做同样的事情则需要花上30天的时间。

本书的其余部分将更详细地说明我们所提供和推荐的建议,但希望本章可以从总体上给你一个切合实际的蓝图。尽管隐去了一些信息以免泄露商业内容,但我们在这里所提到的项目都是真实的案例,且不会夸大任何技术细节或技术价值。

目录

  • 版权声明
  • 献词
  • 对本书的赞誉
  • 译者序
  • 马丁·福勒序
  • 致谢
  • 前言
  • 第一部分 基础篇
  • 第 1 章 软件交付的问题
  • 第 2 章 配置管理
  • 第 3 章 持续集成
  • 第 4 章 测试策略的实现
  • 第二部分 部署流水线
  • 第 5 章 部署流水线解析
  • 第 6 章 构建与部署的脚本化
  • 第 7 章 提交阶段
  • 第 8 章 自动化验收测试
  • 第 9 章 非功能需求的测试
  • 第 10 章 应用程序的部署与发布
  • 第三部分 交付生态圈
  • 第 11 章 基础设施和环境管理
  • 第 12 章 数据管理
  • 第 13 章 组件和依赖管理
  • 第 14 章 版本控制进阶
  • 第 15 章 持续交付管理
  • 参考书目