DevOps实践指南
- Part 4 第二步 :反馈的技术实践
- 17. 将假设驱动的开发和A/B测试融入日常工作
- 17.1 A/B 测试简史
- 17.2 在功能测试中集成 A/B 测试
- 17.3 在发布中集成 A/B 测试
- 17.4 在功能规划中集成 A/B 测试
- 17.5 小结
- 18. 建立评审和协作流程以提升当前工作的质量
- 18.1 变更审批流程的危险
- 18.2 “过度控制变更”的潜在危险
- 18.3 变更的协调和排程
- 18.4 变更的同行评审
- 18.5 人工测试和变更冻结的潜在危害
- 18.6 利用结对编程改进代码变更
- 18.7 消除官僚流程
- 18.8 小结
- 18.9 第四部分总结
Part 4 第二步 :反馈的技术实践
本章将探讨如何通过遥测进行假设驱动的开发和 A/B 测试,帮助我们实现组织的目标并在市场中赢得胜利
17. 将假设驱动的开发和A/B测试融入日常工作
在软件项目中,开发人员往往花几个月或几年的时间开发功能,期间经历多次发布,却从未确认过业务需求是否得到了满足,例如某个功能是否达到了所期望的效果,甚至是否被用过。
更糟糕的是,即使发现了某个功能没有达到预期的效果,开发新功能的优先级也可能高于对它进行修正的优先级,结果导致那些效果欠佳的功能永远也无法实现预期的业务目标。
17.1 A/B 测试简史
A/B 测试,简单来说,就是为同一个目标制定两个方案(比如两个页面),让一部分用户使用 A 方案,另一部分用户使用 B 方案,记录下用户的使用情况,看哪个方案更符合设计。
一项极其强大的用户研究技术是定义客户获取渠道并执行 A/B 测试。A/B 测试技术是在直效营销中率先使用的,它是两大类营销策略之一。另一类称为大众营销或品牌营销,通常通过向公众投放尽可能多的广告来影响人们的购买决策。
有良好文档记录的 A/B 测试案例包括竞选筹款、互联网营销和精益创业方法论。有意思的是,英国政府也采用 A/B 测试来确定哪些信件能最有效地收回逾期税收。
17.2 在功能测试中集成 A/B 测试
在现代用户体验实践中,最常用的 A/B 测试技术是,在一个网站上,给访问者随机地展示一个页面的两个版本之一,即控制组(A)或实验组(B)。基于对这两组用户后续行为的统计分析,可以判断这两者的结果是否存在显著差异,从而找出实验组(例如,功能的变化、设计元素、背景颜色)和结果(例如,转化率、平均订单大小)之间的因果联系。
有时,A/B 测试也称为在线控制实验和拆分测试。在实验的过程中还支持多个变量,从而观察变量之间的相互作用,这种技术称为多变量测试。
“在评估旨在提高关键指标的设计良好且良好执行的实验后,只有约三分之一的功能成功地提升了关键指标!”换句话说,其他三分之二的功能所产生的影响可以忽略不计,甚至会使情况变得更糟。“极端地说,与其构建这些没有价值的功能,还不如让整个团队好好地度个假,这样对组织和客户而言反而更好。
在进行产品研发以前,还有许多其他进行用户研究的方式。最廉价的方法包括进行调查、创造原型(使用 Balsamiq等工具进行模拟,或使用代码编写的可交互版本)以及进行可用度测试。谷歌的工程总监 Alberto Savoia 创造了“原型法”这个术语,指的是通过原型来验证当前是否在创造正确的东西。相对于编码实现一个无用的功能,用户研究非常廉价而且容易实现。所以,几乎在任何情况下,都不应该未经验证就设置功能的优先级。
我们的对策是将 A/B 测试整合到设计、实现、测试和部署功能的过程中。通过进行有意义的用户研究和实验,确保我们的努力有助于实现客户和组织的目标,并能帮助我们赢得市场。
17.3 在发布中集成 A/B 测试
通过在生产环境中快速轻松地按需部署,利用特性开关将软件的多个版本同时交付给多个用户群,可以进行快速、迭代的 A/B 测试。要实现这一点,需要在应用程序栈的各个层次上进行全面的生产环境遥测。
通过勾选特性开关中的选项,可以控制能看到实验组版本的用户比例。例如,可以让一半的客户成为实验组,向其显示“与购物车中失效商品相似商品的链接”。在实验中,我们对比控制组(无选择)和实验组(有选择)的用户行为,可能是衡量在此期间的购买数
“实验的目的是做出明智的决策,确保向数百万的会员推出可用的功能。我们经常在一些功能上投入了大量时间,而且不得不维护,但没有证据表明它们是成功的或者受到了用户的欢迎。A/B 测试让我们可以在开发过程中就判断一项功能是否值得继续投入。”
17.4 在功能规划中集成 A/B 测试
一旦拥有了支持 A/B 功能发布和测试的基础设施,我们还必须确保产品经理将每个功能都视为一个假设,并基于在生产环境中实际的用户实验结果来证明或反驳这些假设。构建实验应该在客户获取渠道的整个背景下设计。
软件开发生命周期(Software Development Life Cycle,SDLC)包含了软件从开始到发布的不同阶段。它定义了一种用于提高待开发软件质量和效率的过程。因此,SDLC旨在通过最少的资源,交付出高质量的软件。为了避免产生严重项目失败后果,软件开发的生命周期通常可以被划分为如下六个阶段:
- 需求收集
- 设计
- 软件开发
- 测试和质量保证
- 部署
- 维护
17.5 小结
成功不但需要快速地部署和发布软件,还要在实验方面超越竞争对手。采用假设驱动的开发、定义和度量客户获取渠道,以及 A/B 测试等技术,能够安全、轻松地进行用户实验,从而让员工发挥出创造力和创新能力,并进行组织性学习。虽然成功很重要,但源于实验的组织性学习也能让员工积极主动地去实现业务目标和客户满意度。下一章将通过研究和创建评审和协作流程,来提高当前工作的质量。
18. 建立评审和协作流程以提升当前工作的质量
本章的目标是帮助开发人员和运维人员在实施生产环境变更前降低变更的风险。按照传统的做法,当我们评审将要部署的变更时,往往主要依赖部署之前的评审、审核和审批环节。审批者通常来自外部团队,他们对实际工作不了解,所以其实无法准确评判变更是否有风险,而且为了获得全部必要的审批需要花费不少时间,这进一步延长了变更的交付时间。
GitHub Flow 由如下 5 个步骤组成。
(1) 工程师为了实现一项新功能需求,要基于主干建立一个命名清晰的分支(例如,newoauth2-scopes)。
(2) 工程师提交代码到本地分支,并定期将工作成果推送到远端服务器的同名分支上。
(3) 当他们需要反馈或帮助时,或者准备将这个分支的代码合并到主干时,就会提交一个新的 Pull Request。
(4) 在他们获得期望的评审并通过必要的审核后,就可以将代码合并到主干了。
(5) 一旦将代码变更合并进了主干,工程师就可以将其部署到生产环境了。
本章会将诸如 GitHub 的实践整合到日常工作中,我们将摆脱对定期检查和审批的依赖,用不间断的同行评审取而代之。我们的目标是确保开发人员、运维人员和信息安全人员始终紧密协作,从而使系统所做的变更可靠、安全、符合设计。
18.1 变更审批流程的危险
Knight Capital 的宕机事件是近期最突出的软件部署事故之一。15 分钟的部署事故造成了 4.4亿美元的交易损失,在此期间工程团队也无法终止生产服务。财务损失使公司的运营陷入困境。为了继续经营下去,避免危及整个金融系统,该公司在一周之后被迫出售。
对于事故发生的原因通常有两种反事实的叙述
- 第一种叙述是:此次事故是由于变更控制失效导致的。这听起来很合理,因为我们可以想象,如果采用更好的变更控制实践,就能够更早地识别出风险,并阻止将变更部署到生产环境。如果我们无法阻止部署,则可以采取其他措施实现更快的检测和恢复
- 第二种叙述是:此次事故是由于测试失败导致的。这似乎也有道理,因为通过更完备的测试实践,就可以更早地发现风险,并取消这次有风险的部署,或者至少可以采取某些措施来实现更快的检测和恢复
反事实思维是心理学术语,指人们往往针对已经发生的生活事件创建其他可能的叙述。在可靠性工程中,反事实思维通常涉及对“想象中系统”而非“现实系统”的叙述。
但现实是,在低信任度的指挥与控制型文化环境中,这些变更控制和测试实践反而会增加问题复发的几率,甚至造成更严重的后果。
18.2 “过度控制变更”的潜在危险
传统的变更控制可能会导致意想不到的后果,例如延长交付时间,降低部署过程中反馈的强度和即时性。为了更好地理解这是怎么发生的,我们回顾一下在变更控制失败发生时通常正在实施的控制。
- 在变更请求表单中添加更多需要回答的问题。
- 经过更多重授权,例如多加一级管理层(例如不但要运营副总裁批准,还需要首席信息官的批准)或更多利益干系人(例如网络工程、架构评审员会等)的审批。
- 变更审批需要更长的前置时间,这样变更请求才能被适当地评估。
这些控制带来了大量额外的步骤和审批,增加了部署过程的阻力,同时增加了批量尺寸和部署的前置时间。我们知道,对于开发和运维来说,这降低了收获成功的工作成果的可能性。这些控制也降低了我们获得反馈的速度。
丰田生产系统的核心理念之一是“最了解问题的人通常是离问题最近的人”。随着工作和工作系统变得越来越复杂和动态——这在 DevOps 的价值流中是很典型的,这个道理就越发显而易见。在这种情况下,让距离工作越来越远的人来做相关审批的步骤,这实际上可能会降低工作成功的概率。就像之前就已经证明过的一样,开展工作的人(即变更实施者)和决定做这项工作的人(即变更授权人)之间的距离越远,审批流程的结果就越差。在 Puppet Labs 发布的《2014 年 DevOps 现状报告》中,一项主要的发现是高绩效组织更多地依赖同行评审,更少地依赖外部变更批准。
出于上述所有原因,我们需要创建的控制实践应该更类似于同行评审,减少对外部相关组织变更授权的依赖。此外还需要有效地协调和安排变更相关的活动。
18.3 变更的协调和排程
每当多个团队在共享依赖关系的系统上工作时,就可能需要协调变更,以确保它们不会相互干扰(例如,编组、批处理和变更的排程)。一般来说,组织的架构越是松耦合,组件团队之间需要沟通和协调的事情就越少。当系统架构真正做到了以服务为导向时,每个团队就可以进行高度自主的变更了,因为局部的变更不太可能造成全局的中断。
对于复杂的组织,以及系统架构耦合程度高的组织来说,我们可能需要更加小心地来安排变更。各个团队的变更代表要聚在一起,他们并不是做变更授权,而是做变更工作的排程和序列化,目的是最小化事故风险。
然而,在某些领域,诸如底层基础设施变更(例如核心网络交换机变更),将总是伴随着较高的风险。这类变更将始终需要技术性的保障措施,如冗余备份系统、故障切换、综合测试和变更模拟(理想情况下)
18.4 变更的同行评审
与在部署之前需要外部组织的审批不同,同行评审是要求工程师请同行对他们的变更进行评审。在开发中,这种实践被称为代码评审,但它同样适用于对应用程序或环境(包括服务器、网络和数据库)进行的任何变更。同行评审的目标是通过工程师同事的仔细核查来减少变更错误。这种形式的评审不仅提高了变更的质量,还相当于进行了交叉培训,对互相学习和技能提升非常有好处
进行同行评审的合理时机,是将代码向版本控制系统中的主干提交时,这个时候变更可能会影响到整个团队,或者造成全局影响。至少,工程师同事应该审核我们的变更,但是对于风险更高的领域,例如数据库变更,或者在自动化测试覆盖率不高的情况下对业务的关键组件进行变更,可能就需要领域专家(例如信息安全工程师、数据库工程师)做进一步的审查,或者做多重评审(例如,用“+2”做评论,而不是“+1”)
保持小批量尺寸的原则也适用于代码评审。变更的批量越大,评审工程师理解这些工作需要花费的时间就越长,他们的负担也越大。
代码评审的指导原则如下
- 每个人在将代码提交到主干以前,必须要有同行来评审他们的变更(例如代码、环境等)。
- 每个人都应该持续关注其他成员的提交活动,以便识别和审查出潜在的冲突。
- 定义哪些变更属于高风险的变更,从而决定是否需要请领域专家(例如数据库变更、安全性敏感的身份验证模块等)来进行审查。
- 如果提交的变更尺寸太大了,以至于让人很难理解——换句话说,阅读了几遍代码还无法理解,或者需要提交者进行解释——那么这个变更就需要分解成多个较小的变更来提交,使之一目了然。
为了避免形式主义的评审,可能还要检查一下代码评审的统计数据,从而确定有多少个代码提交通过了评审,有多少个没有通过,也可以对特定的代码评审进行抽样和检查,代码评审有如下几种形式
- 结对编程:程序员结对地在一起工作(见下节)。
- “肩并肩”:在一名程序员编写了一段代码后,评审程序员接着就逐行阅读他的代码。
- 电子邮件送审:在代码被签入到代码管理系统中后,系统就立刻自动向评审者们邮寄一份代码
- 工具辅助评审:编码者和审阅者都使用专门用于代码评审的工具(例如,Gerrit、GitHub的 Pull Request 等)或由源代码仓库(例如,GitHub、Mercurial、Subversion,以及 Gerrit、Atlassian Stash 和 Atlassian Crucible 等其他平台)提供的类似功能。
通过对变更进行各种形式的仔细检查,有助于发现那些曾经忽视的错误。代码评审还可以辅助增量代码的提交和生产环境部署,并支持基于主干的部署和大规模持续交付。
严格的纪律和强制性的代码评审,这涵盖以了下几个方面:
- 语言的代码可读性(强制编码样式);
- 代码分支所有权的分配,并负责保证一致性和正确性;
- 在团队中提倡代码的透明度和贡献度。
图 18-3 显示了代码变更的大小是怎样影响代码评审的前置时间的。x 轴表示代码变更的大小,y 轴表示代码评审过程的前置时间。一般来说,需要代码评审的变更批量越大,代码评审所需的前置时间就越长。左上角的数据点表示更复杂和更具潜在风险的变更,它们需要更多的审议和讨论。
18.5 人工测试和变更冻结的潜在危害
现在,我们已经建立了同行评审,用来降低风险,缩短与变更审批流程相关的前置时间,并实现大规模的持续交付,就像在谷歌的案例研究中看到的效果。下面让我们来看一下测试弄巧成拙的情况。在测试失败时,我们通常的反应是应该做更多的测试。但是,如果只是在项目结束时去进行更多的测试,可能会导致更糟糕的结果。
尤其在做人工测试的时候更是如此,因为人工测试本来就比自动化测试更慢、更乏味,而且完成“附加测试”的时间通常更长,这意味着部署频率更低,进而部署的批量尺寸也增大了。从理论和实践两个方面讲,我们都知道部署的批量尺寸越大,变更的成功率就越低,事故发生的数量和平均故障恢复时间(MTTR)也都随之上升——这个结果与我们期望的恰恰相反。
我们不想在变更冻结期间安排大量的变更测试,而是要全面测试我们的日常工作,作为顺利连续生产过程的一部分,同时提高部署的频率。通过这种方式,我们可以实现内建质量,能够以更小的批量尺寸进行测试、部署和发布。
18.6 利用结对编程改进代码变更
结对编程就是两名软件开发工程师同时在同一台工作站上工作的开发方法,它是一种在 21世纪初由极限编程和敏捷开发广泛推广的实践。与代码评审一样,这种实践始于开发过程,但是在价值流中,它也适用于与工程师相关的其他工作。在本书中,术语结对和结对编程的含义是相同的,以表明这种实践不只适用于开发人员。
在常见的结对模式中,一名工程师扮演驾驶者角色,他是实际上编写代码的人,而另一名工程师则作为导航员、观察者或督导者,他会检查驾驶者正在进行的工作。在检查的过程中,观察者也可以根据工作的战略方向,提出改进的思路以及将来可能遇到的问题。有了观察者作为安全网和指导,驾驶者可以将全部的注意力都放在完成任务的战术方面。当两人具有不同的特长时,他们可以互相取长补短,不管是通过专门的培训还是通过分享技术和变通办法。
另一种结对编程的模式是通过测试驱动开发进行的,这是指一名工程师编写自动化测试,另外一名工程师编写代码。
“如果可以选择的话,大多数人很可能会放弃代码审查,而在结对编程时,这是不可能的。结对的双方都必须理解当下的代码。结对可能是侵入性的,但这也会迫使人们达到前所未有的沟通水平。”
结对编程还能有益于知识在组织内的传播,以及信息在团队内部的流动。让经验丰富的工程师在经验不足的工程师实现代码时同步地评审,这也是一种有效的教学和学习的方式。
许多组织能正常地执行代码评审,他们靠的是一种文化,即认可评审代码的价值不低于编写代码的价值。当尚未建立这种文化时,在过渡时期,结对编程可以作为一种宝贵的实践。
评估 Pull Request 的有效性
因为同行评审是我们控制环境的重要组成部分,所以需要确保它能有效地工作。
- 一种方法是在出现生产中断的时候,检查任何与之相关的变更,并检查所有相关的同行评审过程。
- 另一种方法来自 Ryan Tomayko,他是 GitHub 的首席信息官和联合创始人,同时也是 Pull Request 处理的发明人之一。当他被问到一个糟糕的 Pull Request 和一个有效的 Pull Request 有何差异时,他说,这与它们在生产环境中的结果其实没有什么关联。相反,一个糟糕的 Pull Request其实没有为读者提供足够的内容,有些甚至连说明变更目的的文档都没有。例如,一个Pull Request仅仅提供了这样的文字:“修复故障单#3616 和#3841”。
在被要求描述一个好的、表明评审过程有效的 Pull Request 时的基本要素:
- 必须足够详细地说明变更的原因
- 如何做的变更
- 任何已识别的风险
- 应对措施
18.7 消除官僚流程
到目前为止,我们已经讨论了同行评审和结对编程的流程,它们能够在不需要依赖外部变更审批的情况下,提高工作质量。然而,许多公司仍然存在着由来已久的、动辄需要数月的审批流程。这些审批流程大大延长了前置时间,不仅阻碍了快速地向客户交付价值,可能还给实现组织目标平添了风险。当发生这种情况时,我们必须重新设计流程,才便更快、更安全地实现目标。
18.8 小结
本章讨论了一些要整合到日常工作中的技术实践,它们能够提高变更的质量,降低部署出错的风险,减少对审批流程的依赖。GitHub 和塔吉特的案例研究表明,这些实践不仅改善了工作成果,而且还极大地缩短了前置时间,并提高了开发人员的生产力。这些工作需要高度信任的文化。
创造条件,让变更实施者完全掌控自己的变更质量,这是我们努力建设的高度信任的、生成性文化的重要组成部分。而且,这些条件能使我们创造出一个更加安全的工作系统,我们在实现目标的过程中相互帮助,跨越任何必须跨越的边界。
18.9 第四部分总结
第四部分向我们展示了,通过实施反馈回路,可以让每个人都为实现共同的目标而协作,当发生问题时及时发现问题,并通过快速检测和恢复的机制,保障所有功能不仅能按照设计在生产环境中运行,而且还实现了组织目标和组织学习。我们还研究了如何让开发和运维共享目标,从而提高整个价值流的健康程度。
我们即将进入第五部分“第三步:持续学习与实验的技术实践”,以便更早、更快、更廉价地创造学习机会,这样才能打造创新和实验文化,使每个人都通过从事有意义的工作,帮助组织取得成功