在这里,我们就将涉事企业姑且称为“某公司”。至于发现问题的开发人员,我们也隐去真名,称其为王二。
在本文中,我们借此机会聊聊软件开发中的人为错误,以及针对这些错误应当采取的预防措施。
从开发到生产
首先某公司部署代码库变更的基本流程总结如下:
开发人员测试变更内容(属于流程中的常识性步骤,因此不做强调)。
代码审查(仅需要一名审查员,用于审查的时间一般也不长)。
将代码 push 至预生产环境。测试预生产环境。
在 push 至生产环境前重新审查变更清单。
在 push 至生产环境过程中重新审查变更清单。
在 push 至生产环境之后重新审查变更清单。
飞行员起飞前的检查清单列表里有一句话:
测试标准实践明确指出,飞行员必须妥善记录变更清单。
毫无疑问,飞行员在起飞之前当然得对飞机进行一番全面检查,这是毋庸争论的事实。如果飞行员没有负起检查责任,或者机场工作人员坦言为了避免晚点,检查过程中省略了某些步骤,乘客怎么敢贸然登机?反正我是不敢。但不少事故记录显示,某些飞行员就是没能尽到机体检查义务,最终引发了可怕的悲剧。
某公司虽然不是航空企业,犯错的代价也不是宝贵的性命,但事故仍然严重影响到企业的业务与信誉。如果情况再严重一些,这家公司可能将彻底不复存在。
总而言之,在“某公司”里,存在着三份变更清单:
1)预生产清单;2)生产中清单;3)生产后清单。
预生产变更清单:
在团队日历当中安排 push 至生产环境的时间。
对 PR 进行审查与批准。
相关团队测试并批准各项变更。
由指定的测试人员确保所有测试用例均正常通过并符合要求。
提交申请与测试或者生产环境无冲突。
在台式机及移动设备上运行冒烟 / 回归测试。
生产中变量清单:
发布流水线中无即时事故(build 失败)。
在此期间,不存在其他部署申请。
在 Slack 中通知部署已经开始。
按下“红色按钮”开始 push 至生产环境。
生产后变更清单:
检查部署脚本日志以了解部署是否成功。
在台式机及移动设备上运行冒烟 / 回归测试。
以一小时为周期,监控所有日志及统计图表。
通知变更相关团队。
如果没有问题,在 Slack 当中宣布部署成功。
很明显,其中不少步骤都有可能发生问题,而产生问题的原因就是执行人员看不到环境的整体情况。
下面,我们从三个角度提出问题:
为什么会出问题?
本应以怎样的方式加以预防?
某公司在事后做了什么?
事故分析
先来看看问题为什么会发生,又怎么会导致大量日志丢失。
PR 当中包含对某行代码的微小变更,可能影响巨大,但相关描述非常模糊。
在同一 PR 当中,三分之一的程序流并未经过王二的实际测试,因为他觉得抽查当中没发现问题就够了。
代码审查员没有注意到这一微小的变更。
王二并没有在测试中检查这些即将受到毁灭性影响的日志(注意:公司当中根本没有质量保证人员这一职务)。
身兼测试人员与代码审查员两职于一身的王二没有注意到代码未发送记录请求的问题(注意:因为没有质量保证人员,所以代码审查员必然同时兼任测试人员)。
在 push 至生产环境后的监控环节当中,由于图表中的变化与其他长期记录相比非常微小,所以日志记录丢失问题没能被及时发现。
不存在对日志及图表的每日监控机制,因此没人第一时间注意到这个问题。
下一条部署至生产环境的 PR 同样没有正确遵循检查流程,且 / 或测试人员与第 6 条一样未能发现图表中的微小变化。
本应以怎样的方式加以预防
本应执行周期为七天的日志检查
开发人员往往只会检查发布过程中 1 到 4 小时周期内的日志。这就意味着,如果 4 小时前生产系统曾经发生某些异常,那么测试人员将无法发现。理论上来说,这样的问题忽略循环可能永远存在。
事实上,王二在后来对七天内日志记录进行检查时,发现了多项错误。但到目前为止,某公司还没有任何人——从管理层到开发人员——认为有必要把监控周期设定为七天。时间本身也并不是问题,开发人员可以快速切换日志显示区间来查看过去 1 小时、1 天或者 7 天当中的记录内容。
每日监控
目前公司内缺少适当的每日监控机制。监控只在清单列出的变更周期内进行,此外不再单独投入时间。一种解决办法,就是在每天工作结束时强制要求检查日志内容。
我在拉脱维亚的 Evolution Gaming 公司工作时,团队采取的是职务轮换机制,大家轮流负责检查 Sentry 错误日志、审查待处理的 PR、向团队通报审查结果以及进行日常维护等等。在整个冲刺周期(2 周)当中,大家一直身兼常规职务与每日轮换职务两种角色。为了确保所有要求都能得到严格遵循,我们还在流程中添加了一种游戏化机制,即每一次 PR 审查都对应一定的分数奖励。
虽然大家都承认审查工作的意义,但在实际执行中,大多数人其实并不情愿把时间“浪费”在这件事上。因此,激励制度是种提升审查参与度与积极性的好办法。
某公司也可以在监控方面采用相同的方法。毕竟对大多数企业来说,用稍稍放慢一点速度的代价换取更高的发布质量无疑是划算的,而且从长远来看对每个人都有利。
双批准或者多批准模式
在我待过的团队中,但凡代码审查效果出色的,都至少要对每条 PR 进行双重批准。事实证明,这种多轮把关的方法相当可靠,因此我后来自己单飞后,也继续在个人外包合同中继续沿用这一模式。
单批准模式的主要问题在于……举个例子,大家见过只有一名飞行员的航班吗?万一其中一人忘记开启控制面板上的某个开关,至少还有另一位及时提醒提醒。
提高测试质量
无论是单元测试还是集成测试,我们都该将其视为保证生产安全的一道有力屏障。
很明显,某公司内的集成测试并没有覆盖到代码中的重要变更部分。事实上,这家公司压根就没把集成测试纳入王二团队的测试范畴,甚至多次打回了要求进行集成测试的申请。这并不是因为集成测试在技术上有多困难,只是因为上下级沟通不畅、缺乏主动性以及无知。
从主观上讲,大多数开发人员不愿进行测试的头号原因,是“我为什么要在代码已经能够正常运行的情况下,因为「不必要的麻烦事」而拖慢部署进度?”
而第二号原因,就是截止日期被压得太紧。
说起单元测试,很多朋友都觉得这就是走个过场,毕竟我们很难量化单元测试到底阻断了多少错误。
这里要强调一下,单元测试确实可以防止开发期间出现的大量错误。我们的项目需要在 push 至生产环境之前就进行过测试,这样即使 1 项测试失败,合并尝试都将中止。这就给了我们解决问题并再次 push 的时间窗口。
更重要的是,我们可以在测试运行期间严密观察。我发现很多开发人员没有养成在后台运行测试的好习惯,而由此带来的恶果,就是在问题出现后大家需要耗费大量时间回溯一切变更。
当然,即使是在观察当中,出现的大量问题也有可能让我们迷失在修改的漩涡当中。这个问题可以通过科学的重构方法进行预防。Martin Fowlers 在书中建议以积极的心态进行重构,例如在发生测试失败时,请还原最后一项变更并确保不影响测试环境。他提出的方法可以概括为“一次只改一行”。当然,我们也可以根据实际情况灵活调整,比如“一次只改两行”之类。
接下来是 TDD。有研究表明,TDD 能够帮助我们将项目中的 bug 减少 40% 到 90%。但这种从长远来看收益丰厚的方法,却往往遭到开发人员的激烈反对。不过如果各位不打算破产或者失业,还是请把 TDD 严格贯彻到位。较高的测试标准,也将为您带来更强的比较竞争优势。
实施集成测试
当然,单元测试可能很难覆盖到某些代码流,甚至相关测试更像是集成 / 单元测试的结合体。这确实是种比较棘手的情况,这里我推荐大家了解 Sandy Metz 给出的建议:
如果能在开发过程中节约成本,不妨打破规则。——Rails Conf 2013 大会,Sandy Metz
我可能会考虑在单元测试中采用集成测试方法来覆盖这次引发问题的代码行,从而尽量缩小范围。对问题进行充分记录,并在测试完成后尽快清除集成测试部分。
当然,我并不是集成测试方面的专家,但在这种情况下,集成测试确实能够防止数据日志丢失。
极限掌控力
我想聊的最后一点,来自 Jocko Willinks 的《极限掌控力》一书。我坚信,变更清单检查不足的责任,有相当一部分要由团队主管(包括技术主管或者项目经理)以及开发人员本身来承担。
开发人员与质量保证人员之所以没有严格遵循检查流程,是因为他们并不清楚这项工作的重要意义,以及一旦出现偏差有可能带来怎样的严重后果。当然,这也可能是因为主管人员不太了解流程中的某些组成部分。在这种情况下,开发人员与质量保证员必须抱有“一路上报,直到解决问题”的坚定态度。
某公司在事后做了什么
在这条 PR 被 push 至生产环境的七天之后,某公司决定开发用于比较服务器与客户端日志内容的系统。这套系统能够在服务器与客户端日志间存在差异时及时发出通报。
王二团队开始引入每周日志监控流程,但是主管仍然没有亲自跟进这项工作。
幸运的是,公司管理层已经开始进行对话,探讨如何解决未来可能出现的类似问题。
在业务流程改善方面,存在着大量可供企业选择的具体方案,而这些方案能够有效预防代码引发的内部或者外部事故。但遗憾的是,我们人类天然具有一种懒性或者说消极性,那就是只会在遭遇灾难之后才真正意识到预防准备的重要意义。
原文链接
We Crashed and Lost All Essential Data Logs. Where Did We Go Wrong?
评论