【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

避免热修复部署的方法

  • 2016-06-13
  • 本文字数:6614 字

    阅读完需:约 22 分钟

我们,包括我的团队以及我本人,都很恐惧重新部署!设想一下吧,我们不是在做一项新特性,而是必须要重新部署系统:版本管理、要求 scrum master 编写变更请求、对系统进行打包、跑到 IT 运维团队那里,请求他们帮着进行部署。到一天结束的时候,会发现燃尽图进度不理想,所有的利益相关方都会感到不爽。

作为一名软件工程师,非常不幸的一件事情在于软件是看不见摸不着的。它的这一特点,使得我们很难看到它所具有的漏洞。当在生产环境发现这些漏洞的时候,如果对其打补丁或进行热修复的话,将会导致费时费力的重部署。本文将会展示一些拿来即用的技巧,这些技巧能够帮助我们减少热修复部署的需要。

但是 Bug 无处不在

在作为软件工程师的职业生涯中,我看到过各种类型的错误,或者工程师所谓的“ bug ”。它们其实无处不在:在服务器端、在客户端、在浏览器上、在网络设备中!

Bug 不仅无处不在,它们还会带来一定的成本。发现它们、评估它们的影响以及对其进行修正(也就是调试的过程)都是成本高昂的。 NASA 曾经经历过史上成本最高的 bug 之一,1.25 亿美元消散在了火星稀薄的空气之中。

但是,“人都会犯错的”,NASA 项目的 JPL 主管 Tom Gavin 曾经这样说道。不幸的是,软件工程的本质特点让我们很难在开发的早期就探测到潜在的 bug。

我们可以采取一些措施,降低不必要的 bug 侵入到软件中的可能性。这能最小化热修复部署的风险。本文在三个方面讨论了这些措施:

  1. 开发期
  2. 部署之前
  3. 部署之后

开发期

从一个清晰的 Story 开始

一个好的 Story 对于什么是完成有着良好的定义,并且可以进行拆分。好的 Story 会在以下方面给团队带来帮助:

  1. 减少歧义性;
  2. 帮助团队理解 Story 的目的;
  3. 让开发过程更加快速;
  4. 支持更高质量的交付。

好的 Story 首先要有一个好的标题,通常格式如下:

作为一个 [角色],我想要 [活动], 以便于实现 [目标 / 价值]

接下来,它需要有一个完成状态的良好定义。完成状态的定义能够告诉工程团队每个可测试的场景,在这个 Story 可交付之前,必须要包含这些场景。完成状态的定义是由 product owner 编写的,并且会由其进行检查。

工程团队需要将任务进行拆分。列表中要包含构建该 Story 的技术步骤。需要注意的是,每一项任务应该在一天之内完成,这是一项任务完成时间的上限——如果做不到的话,那就要进行进一步地拆分。这样的话,product owner 能够更容易地跟踪进度,也能让团队感到有一种成就感。

而定义较差的 Story 一般会具有下面的特点:

  1. 不清晰,Story 的目标是什么?
  2. 缺少总体的样例。甚至连包含预期输出的简单样例都没有。
  3. 完成的定义模棱两可,不知道预期结果是什么。

例如,有一个 Story 是要对系统 A 进行变更的,但是没有提及这个变更会对同一团队维护的系统 B 产生影响。因此,就会出现 bug!但是,如果系统 B 是由其他的团队来维护的话,又该如何防止这种问题的出现的呢?我们稍后将会对其进行进一步的讨论。

不要匆忙了事的文化

这是除了 Story 定义之外,最重要的事情。我首先给你讲述一下我们的经历,我们是如何通过不那么急切行事,避免热修复部署的。

以前,我们在进行部署的时候,都会弥漫着一种紧迫感。其实就是在部署的那一天,希望包含尽可能多的 Story,我们为此感到了很大的压力。这样的结果就是对细节失去关注,我们经常需要重新部署。有时候,我们不得不部署三次来进行热修复,这真的让人非常尴尬。

但是,当我们开始按照稳健的节奏工作时,就发现很少再需要热修复部署了,当然偶尔会发生的安全缺陷除外。我们非常冷静地进行判断:如果某个 Story 无法进行全面充分的测试,那么在部署时就不会包含该 Story,我们会等待下一个周期。我们非常坚定地执行这个规则,因此很少需要重新部署。

最好是一次部署成功,而不是匆忙进行部署,却要失败好多次。

这里的关键在于分配优先级,那是怎么做的呢?

要事优先。不要让客户、商业方案、负责客服的家伙或者其他人告诉你“这个特性必须要立刻加到生产版本中”。对于这种情况,要想办法对他们说不。很可能出现的情况是,即便你交付了这个特性,它在几个月内也完全用不着。

但是,有些特性、修复以及缺陷确实需要快速响应,关键就是确定优先级!我们通常有四个层级的优先级:P0、P1、P2 和 P3。

如果有紧急的 Story,并且需要快速部署的话,那为其分配的优先级就是 P1。例如,如果修改了登录页的代码之后,潜在用户无法进行注册了,那么就要将这个 bug 考虑作为 P1 级别。

比 P1 更高的是 P0。P0 是非常、非常严重的问题。它可能会导致整个系统完全无法使用,可能会导致很多客户暴跳如雷,可能会让你的公司破产。如果这个问题需要你立即从床上跳起来去解决,那它就是我所说的这种类型。

P0 级别 bug 的一个样例就是系统在部署之后,所消耗的内存空间呈指数级增长,导致性能的下降,所有的用户均无法可靠地访问系统。这需要“紧急集合(all hands on deck)”式的响应,快速修正问题(但是不要恐慌)并尽可能快速地部署热修复版本。

P2 是针对当前 sprint 的,在 sprint 开始之前,所有 P2 的 Story 都经过了慎重地考虑,按照规划,它们需要包含在当前 sprint 之中。

比 P2 级别更低的是 P3,它能够等到规划未来 sprint 时再进行考虑。我们该怎样判断某个 Story 是 P3 呢?如果不部署所要求 Story, 有其他方式也能达到预期输出的话,那么这个 Story 就可以进行等待。P3 是默认的优先级。对于未规划的所有特性 /bug 修正的请求,都将其优先级定为 P3。

遵循上述的指导,我们有了更好的精神状态,在团队所编写的代码中,bug 也会更少。

好的编码实践

如果你有一个团队的话,要进行结对编程,至少偶尔要这样做。结对编程有利于知识的共享,新雇员要与团队中经验丰富的编码人员结对。通过这种方式,新雇员能够快速学习到系统中的知识,并且能够开始独立实现特性。有些公司则更进一步,将结对编程变成了常态,为两位开发人员只提供一台计算机,强迫他们进行结对编程。

编写良好的文档良好的提交信息。有很多其他的技术能够指导我们编写更好的代码:不要硬编码值、使用整洁命名的变量,使代码易于阅读等等。寻找好的编码参考实践,并确保整个团队理解为什么使用它们以及如何使用它们。

还要考虑到物理因素。允许每个人按照其最高效的方式工作——如果有人觉得他们在晚上工作状态更好的话,那就让他们晚上工作。要照顾到人的各个方面——好的物理环境、具有安全感的工作文化以及健康的习惯,如保证好的夜晚睡眠质量,这些会让人压力更小,精力更充沛,有利于产生更好的产品。

质量控制

质量控制中最重要的一个方面就是测试。测试分为很多种:集成测试、单元测试、黑盒、白盒以及用户验收测试(user acceptance testing,UAT)。在测试方面,应该是没有谈判余地的。但令人遗憾的是,实际上,通常并非如此。

忽略测试意味着积累技术债。随着时间的推移,开发流程将会逐渐恶化,因为开发人员会担心他们的代码会对其他部分的代码造成破坏。即便测试都能通过,我们依然会持续地发现 bug,到底该如何避免 bug 的出现呢!Djikstra 曾经说过:

测试从来不能证明没有故障,它所做的只能是展现它们的存在。

希望我们能够意识到测试是做什么的,并且能够明白如果没有它的话,软件开发是不完整的。最好要衡量代码覆盖度。代码覆盖度能够探测到哪些代码已经测试过了,哪些还没有测试。从数学上来讲,代码覆盖度可以表示为:

根据经验规则,覆盖度最好要达到 85% 或更高,测试至少要覆盖代码的 85%。

我们并不能保证达到 85% 的代码覆盖率就是没有 bug 的系统,我甚至不敢说代码覆盖度会意味着更少的 bug。不要产生误解,99.99% 的覆盖率并不代表系统少了99.99% 的bug。

与代码覆盖率同样重要的是应该将持续构建工具集成到代码库中,如Travis CI、Drone 或Codeship。这样的话,对分支的任何变化都会触发测试。如果测试通过的话,那么它将会我们构建一个包,CI 致力于最大化工作效率。

作为product owner,请不要在测试方面妥协!借助非常严格的测试,在生产环境测试我们的包的时候,曾经发现过P0 级别的bug。这是在准生产环境(staging environment)和生产环境下,运行测试2 到3 次之后才发现的。

对工作进行同步

在所有的利益相关方之间对工作进行同步是非常重要的,这些相关方包括:product owner、开发人员、scrum master 以及其他团队。实现工作同步的方式之一就是每天举行站会,这种会议会在每天的特定时间举行,每个人要讲述一下昨天做了什么,以及今天计划做什么。

如果你有远程团队成员的话,最好每天进行两次站立会议,一次在早上进行,另外一次在傍晚进行。通过这种方式,就能了解在远程的其他人计划做什么,并且能够在傍晚比较容易地得知其进度。

与之相关的另外一件事情就是跨团队的同步会议,这涉及到相关团队的成员。这个更大型的会议可以每两周举行一次,而不是每天都举行。比如,API 团队以及portal 管理团队之间所举行的会议就是这种类型,portal 管理团队会使用API 团队所开发的API。

站立会议还会进行一些关键性的讨论。如果会影响到相关团队的话,非常重要的一点就是尽可能多的通知相关成员。例如,如果要废弃数据库中的某一列,而这个列需要在多个应用间进行共享,那么提出废弃意见的人需要与其他相关的团队讨论该问题。

捕获更老的bug

你的系统可能已经存有bug,并且积累了一些技术债,这些问题可能会降低开发的速度,或者引起了客户的抱怨。针对这种情况,可以考虑规划整个sprint/ 星期都用来查找关键特性的各个组成部分,然后探讨如何一劳永逸地进行调试并移除bug。

在找到可能会有bug 的特性之后,在to-do 列表中将其记录下来,并为每个条目分配一个优先级。按照sprint 的方式,每次只处理其中一项。如果你的团队中开发人员的数量足够多的话,那么对其进行分而治之是不错的方案:一部分人处理这些琐事(调试),而另一部分人就可以进行正常的story。

我们曾经遇到过来自财务部门相关特性的“bug”,这个问题实际上是由他们自己的错误操作所导致的,例如将钱转到商户的账户之后,错误地点击了取消按钮,而不是完成按钮。为了取消这样的点击,我们需要对数据进行多步手动变更,这最终消耗了我们很多的精力。但是,在进行了两个sprint 的调试之后,我们再也不用进行这些令人郁闷的手动工作了,非常棒!我们已经忘记当时压力重重的状态了,当然,这样我们会更加开心!

自治

这是最后一件事情。强制开发人员从事某一项特性很可能会引入bug。开发人员应该自由选择从事某个新特性、进行研究还是修复某个bug,他们可以选择任意感兴趣的Story。

像Trello、Pivotal Tracker、Asana 这样的工具甚至原始的白板都可以列出当前sprint 要开发的所有特性,然后让大家自由选择他/ 她所感兴趣的Story。就团队来讲,只要Story 符合公司的当前目标,就没有必要由管理人员来分派工作——让大家自行分派任务。

PS:如果你对所有的 Story 都不感兴趣,那么对你来说,这是另外一个“Story”了,HR 也许会非常(不)高兴地找你聊聊。

但是,如果采用自治的策略,所有的人均不想开发某个特性的话,那么 product owner 需要进行讨论来解决这个问题。如果没人愿意做的话,那么需要结对来完成这个 Story。在采用自治策略的情况下,我还没有经历过所有的人均不想选择某个 Story 的场景。

部署之前

Pull 请求以及代码检查

在感觉完成某个 Story 的所有任务之后,你就可以提交一个 merge 请求或 pull 请求。对于具有多个互相关联团队的公司来讲,Pull 请求会更加重要一些。

Pull 请求就绪之后,Story 的状态将会变为“代码检查中”。如果所有的事情看起来都没有问题,那么就会进行合并,然后,如果需要测试的话,product owner 会在准生产环境手动测试 Story。

代码检查不应该包含在某个 Story 完成状态的定义中,就像测试也不应该包含在其中一样。代码检查和测试应该是软件工程师的第二天性(second nature)。所有的 Story 在部署前都要进行代码检查和测试!所以在 Story 完成状态的定义中将其添加进去是多余的。

负责检查的人不应仅关注细节的错误,如变量命名不当或缺少空格,检查者应该关注逻辑错误、如何提升当前代码的性能、可维护性、安全性等方面。

如果你的 Story 采用结对编程的话,那么应该由不和你结对的人来进行代码检查。但需要注意的是,如果采用结对编程依然在代码中出现了逻辑错误,那是一件很令人尴尬的事情,两个人的大脑依然会产生带有 bug 的代码确实很让人难堪。

部署时

部署的内容要小,频繁部署

将你团队的部署计划进行分解。应该考虑每周部署一小块内容,而不是每两周部署一大堆特性,如果可能的话,可以部署地更加频繁。

一次性部署很多 Story 会让用户验收测试耗费太多的时间,用户一般只会觉得“哦,这个特性很棒”,而不是对其进行全面地测试。

另外,如果在这个很大的部署包中出现了 bug,很难有效猜测 bug 产生的根本原因是什么。

在准生产环境测试,在公开环境测试

毫无疑问,更好更安全的方式是不要直接部署到生产环境中。我们应该在准生产环境进行 UAT。如果在准生产环境的测试中发现了带有 bug 的特性,那么就将其排除在部署包之外。

在所有的事情均准备就绪之后,就可以在一台生产环境的机器上进行 dry-run。dry-run 能够让新特性在生产环境中运行,但是不会影响到很多人。网络工程师通常会将系统引导到选定的机器上。如果在 dry-run 环境下出现失败的话,就不要部署这个包!

当你确定所有的事情都达到预期的时候,就可以要求网络工程师将其部署到所有的机器上,使其完全公开。

在部署到所有的机器上之后,邀请一位终端用户来进行再一轮的测试。当然,在很多情况下,这也许不太可行,那也没什么问题。但是,如果你的用户来自公司内部,那么可以选择其中的一位用户进行一下最终测试。

如果遵循上述的所有规则的话,我们很少会在生产环境下发现 bug,但是有些罕见的 bug 只会在生产环境下才会出现,我们曾经见过这种情况。所以,最后一分钟的测试时必要的。

部署之后

使用 bug 跟踪和监控系统

不要让客户成为第一个告诉你软件中存在 bug 的人。如今,我们可以非常容易地实现自己的 bug 跟踪系统,例如,在异常出现的时候,给开发人员发送一封 Email。

像 AppSignals、Raygun、NewRelic 这样的现代化工具都是可以使用的,不过它们通常会有一定的成本。也有一些很好的开源工具可用,不过你需要自行将它们组合起来,如 Munin、Telegraf、InfluxDB 和 Grafana。

通过使用这些工具,我们就能知道出现了某个 bug,而不需用户预先填充一个报告表单。

工作回顾

到此为止,即便不是所有的 Story,至少它们中的大多数都已经部署到了生成环境中。现在是进行快速工作回顾的好时机,与你的团队进行讨论,这包括 product owner、scrum master 以及其他的相关方,讨论一下最近这次部署哪些方面做得比较好,哪里出现了问题,如何在将来最小化错误的出现,以及如何提升团队的工作方式。

如果实现一个系统,允许开发人员在 sprint 的进行过程中就提交要讨论的回顾内容,那是很有用处的。通过这种方式,在进行回顾的时候,人们不用再浪费时间思考回顾点是什么,而是集中精力讨论所提交的内容。

甜点

作为 product owner,不妨好好犒赏一下你的团队,(例如)如果连续三次部署都没有出现热修复的话,领他们喝个咖啡、去餐馆吃顿饭或者给他们放一天假。这会帮助他们提升积极性,让他们产生 bug 更少的代码。这里有个魔咒:要满足工程师,先满足他们的胃。

保持优秀和开放的文化

bug 终究会出现, bug 的发现过程可能会是这样子的:

  1. 业务人员找到你,并且对你说“嘿,我发现了一个 bug。”
  2. “不,这不可能,这种事儿根本就不可能发生!”
  3. 不应该这样啊。
  4. 为什么会这样呢?
  5. 哦,我知道了,原来如此。
  6. 它是如何通过测试的呢?
  7. 对不起,这个特性不是我编写的!

在你的公司和团队中,保持一种开放的文化非常重要。

我们所能做到的就是尽可能减少错误出现的概率,而不是将其完全消除。如果你觉得其他的方式能够降低系统引入 bug 的可能性,从而能够避免热修复部署的话,请在评论区进行讨论。

我希望你们能够像我们一样,早就忘记了上一次热修复部署是什么时候的事情了。简而言之:因为出现 bug 所引起的电话呼叫越少,晚上就能睡得越安稳。

关于作者

Adam Pahlevi的乐趣在于编写具有可读性和高性能的代码。目前,他就职于一家印度尼西亚 / 日本支付网关公司:Veritrans,并担任软件工程师。他曾经出版过图书并发表过很多文章,还担任过 workshop/ 技术大会的演讲者。他使用 Ruby 编程语言。

查看英文原文: The Way to No-Hotfix Deployment

2016-06-13 19:082307

评论

发布
暂无评论
发现更多内容

在线JSON转HTML,TABLE表格工具

入门小站

工具

一文了解 Python 中的对象析构函数:__del__

踏雪痕

Python 3月程序媛福利 3月月更

自己动手写Docker系列 -- 3.3使用命令管道优化参数传递

Docker

自己动手写Docker系列 -- 4.2使用AUFS包装busybox

Docker

测试开发【Mock平台】02基础:Java Spring Boot框架知识

MegaQi

测试工具 测试发开 测试平台开发教程

C#调用C++动态库接口函数和回调函数

DS小龙哥

3月月更

设计电商秒杀系统

supermenG

架构师训练营 4 期

自己动手写Docker系列 -- 3.2增加容器资源限制

Docker

即时通讯(IM)开源项目OpenIM每周迭代版本发布-音视频实时通话-v2.0.4

Geek_1ef48b

优秀程序员的30种思维--技术执念篇(22/100)

hackstoic

技术思维

【C语言】 扫雷游戏(保姆级的实现过程)

謓泽

3月月更

你的 vscode 配置真的舒服么?

道道里

游戏制作片段

都灵树屋

模块 6 作业 拆分电商系统为微服务

王大胖

2022 ARTS|Week 11

MiracleWong

算法 写作 ARTS 打卡计划

DDD 实战(5):战略设计之上下文映射和系统分层架构

深清秋

DDD 软件架构 生鲜电商系统 3月月更

使用Node.js、MongoDB、Fastify 构建API服务

devpoint

mongodb nodejs swagger REST API 3月月更

投资与人性(21/100)

hackstoic

投资

自己动手写Docker系列 -- 4.1使用busybox创建容器

Docker

QT读取剪切板内容-实现复制粘贴文本和图片

DS小龙哥

3月月更

图文详解:阿里宠儿【小兔】RabbitMQ的养成攻略

浅羽技术

Java RabbitMQ 中间件 消息队列 RabbitMQ延时队列

国内外最好用的18个协同办公系统盘点

PingCode

三高Mysql - Inndb存储引擎和索引介绍

懒时小窝

MySQL innodb MySQL InnoDB

在线HTML压缩格式化工具

入门小站

工具

iOS内存管理篇

刁架构

内存管理 iOS 知识体系

Linux之bc命令

入门小站

Linux

自己动手写Docker系列 -- 4.3实现volume数据卷

Docker

一文读懂网关中间件-Nginx

Linux服务器开发

nginx 中间件 api 网关 Linux服务器开发 Linux后台开发

首届实时渲染3D动画创作大赛最佳人气奖?你说了算!

3DCAT实时渲染

3D 虚幻引擎 实时渲染 ue

VuePress 博客之 SEO 优化(四) Open Graph protocol

冴羽

Vue 前端 vuepress SEO 博客搭建

Flutter 封装文本输入框

岛上码农

flutter 跨平台 Android开发 3月月更

避免热修复部署的方法_语言 & 开发_Adam Pahlevi_InfoQ精选文章