速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

程序开发者去世,代码没人懂,一个 bug 导致千万损失

  • 2020-05-09
  • 本文字数:3321 字

    阅读完需:约 11 分钟

程序开发者去世,代码没人懂,一个bug导致千万损失

系统出故障了。当年负责写这个程序的开发者早在十五年前就去世了,现在已经没有人能读得懂他的代码了…


现在一些关键系统的运行仍依赖于过时的软件,但编写他们的人要么离职要么已经去世。中间也缺少维护或更新,导致现在几乎没人能理解它们,而且一旦出现 Bug 就会给企业造成不可挽回的损失。


而现实中的这种例子,远比你想象中的要多。

一个令人深思的故事

我的一位客户负责数项世界排名前一百的养老基金,该公司在前几个月成功的将程序搬到了云端。作为项目的主任架构师,前两天我很意外地直接收到了 CIO 的短信:“抱歉打扰,我们出 S1X 级的大问题了。你能下午飞过来吗?”



“S1X”是他们对“比最严重级别还要糟,级联影响到业务其它非直接相关部分”问题的定义。


事情看起来十万火急,当天晚上我就飞到现场进行了诊断,发现是该客户的系统中一个批处理任务发生了崩溃。


该任务每天晚上执行一次,通过写一个 CSV 文件为某些养老金计算缴费率,再将计算的结果输出到另一个收益(benefit)分配程序。原先收益分配程序设定为在缴费(contribution)低于预测(projection)时会向客户发出报警。由于上一个处理任务已发生崩溃,不再产生输出,因此程序认为“所有缴费为零”。


所以系统立即向该养老基金的经理们发出大量警报邮件,基金经理们被吓得赶紧从该项目里撤离了。


起初没有人找得出问题发生在哪里,在大家的记忆和工作记录中,这个批处理任务也从未崩溃过。编写该批处理程序的人已经在 15 年前离世了,并且数十年前就不再是该企业的员工了。尽管该批处理的程序代码规模不大,但非常难读。因为在编程时主要考虑的是提高计算效率,并未考虑如何适合他人阅读。当然,程序也没有留下任何测试。


事故发生的前一天,脚本在运行环境中的编排发生了一次更改。这被认为是导致事故的罪魁祸首。幸运的是,这个更改做了版本 push,工程部门据此回溯到先前的版本。但不幸的是,这只使事情变得更糟。


最后我们通过提供热修补脚本的方式解决了这个问题。但实质上这次崩溃已经给该基金造成了 170 万美元(约 1203 万人民币)的直接损失。

“数据溃烂”(Bit Rot)问题

事实上类似的故事并非孤例。我在 2012 年离开英特尔加入 Sun 公司,切身体会到他们的 SPARC 产品线做得是多么的糟糕。Sun 在互联网泡沫时代的杀鸡取卵行为,导致此后 SPARC 远远落后于英特尔的至强产品线。


我的经理都和我说,要在英特尔至强服务器上运行模拟程序,因为 SPARC 服务器“非常慢”。更严重的问题在于,英特尔不仅 CPU 性能更好,而且具有制造优势,这意味着 CPU 的制造成本也大大降低。


随之而来的问题很明显:既然远远落后于竞争对手,那么为什么客户还是会购买我们的 SPARC 芯片?一位高级架构师向我给出了令人震惊的答案。那是因为我们的客户的软件系统过于僵化,只能在 SPARC/Solaris 系统上运行。而迁移到 x86/Linux 对客户而言是一项艰巨的任务。许多客户甚至丢失了源代码,无法重新编译应用。


他们能做的最好选择,就是升级到最新一代的 SPARC 处理器,无论这样的处理器性能多慢,价格多么昂贵。


这就是问题所在。我们整个部门的业务模式,完全围绕着这个国家中那些溃烂的软件系统。

维持运转的代价

我加入 Amazon 后,发现自己面对正是这样一个为遗留系统而构建适用的原型。该系统是另一个团队基于大量技术债务开发的,并且团队早已解散。之后该项目的所有权就移交给我们的团队……事实操明,这并非揽下一件好事。所以开发人员陆陆续续跳槽到其他的团队。我加入时的十多名团队成员中,一年后一位都没有留下。


该系统从表面上看并非一无是处。它使用了现代编程语言和技术栈(Java 8)编写,由拿着六位数收入的开发人员所组成的团队进行着日常维护,并不断更新以修复错误和添加新功能。尽管如此,测试修改(turnover)依然是拖累整个系统的显著负担。所有权变更和团队更替导致整体代码设计、端到端功能、最佳实践和调试技术等大量实操知识的丢失。尽管我们一直努力保持项目的推进,但感觉就像进入了一片沼泽地,四处亡羊补牢,深陷战争迷雾(Fog of War)中。


设想一下,一个运行在第一版 Java 上的项目,开发活动几乎为零,没有开发人员负责。还有比这更糟的项目吗?

如何预防发生灾难性故障

软件开发人员致力于构建健壮、无错误的系统,无需过多人工维护就能正常运行多年。据此标准,上面所说的养老金脚本无疑是非常成功的项目。


然而现实很严峻,再好的项目有发生崩溃的一天。最终,所有内容都需要做更新。导致原因可能是:


  • 系统运行所基于的硬件系统停产了。

  • 系统的依赖关系不再可用。

  • 依赖关系中出现了严重安全漏洞,而唯一可用的安全补丁仅适用于并不后向兼容的版本。

  • 应用开发基于一些已不再成立的假设。

  • 甚至是整个世界发生了改变,软件必需因势而变。


无论出于何种原因,变更都是不可避免的。唯一的问题是,当最终需要变更时,它的代价有多大。


对于一个活跃维护的系统,变更就不会那么痛苦。但是,对于一个已有几年甚至数十年没有维护的系统,那么很多因素都可能会导致灾难性的错误。例如:


  • 构建系统的开发人员已经离职。

  • 源码丢失。

  • 开发人员不了解如何正确地编译源码,并构建可执行文件。

  • 开发人员不了解如何部署系统。

  • 开发人员不了解如何正确地配置运行可执行文件。

  • 开发人员对代码的架构和实现一头雾水。

  • 开发人员不了解使代码功能正常运作所依赖的常量和隐含假设。

  • 开发人员不了解如何运行自动化测试。

  • 开发人员不了解如何调试测试问题。

  • 开发人员不了解如何调试生产故障。

  • 开发人员不了解如何获取生产日志和指标度量。


一种解决方案是对上述问题做尽量详细的记录。但文档并非最优的解决方案,因为其中难免会有遗漏。再全面的文档,也比不上自己亲自动手操作。

理想的做法

一个好的开端,就是企业指定专门的开发人员全面负责上述所有问题。但这还不够。


如果仅仅反复“阅读文档”,那么就会产生厌倦。人们并不能从中获得实践经验,进而解决实际问题。


如果加上“绩效审核”,那么人们更倾向于新的出彩项目,很有可能会简单地抹去并掩盖旧的问题,甚至直接从中剔除问题。如果没有真正面对的可交付成果或挑战,许多人自然会选择一条最轻松的道路。


真正要想避免软件发生溃烂,唯一的方法是确保项目的持续推进,即使看起来毫无必要,或是存在风险。建立、维护和验证实操知识和能力的最佳方法,就是不断做出变更,并测试这些变更是否能成功地执行。一旦项目停止推进,那么相关实操知识就会过时和消解。


即使原地打转听上去很可笑,但这对疏于维护而言仍是一种进步。事实上,维护人员总是可以做一些事情实现向前推进,虽然步伐可能很小。


一种做法是使用所有依赖关系的最新版本去更新开发环境,例如:


  • 从 JDK 8 迁移到 11。

  • 更新 JVM,使用 G1 垃圾回收机制替代原先的 CMS。

  • 将 GCC 编译器从版本 5 更新到 7。

  • 将数据库从 Postgres 9.5 更新到 Postgres 11。

  • 将 AWS SDK 从版本 1.10 更新到 1.11。

  • 在生产环境中安装最新的 Linux 发行版。


在一些情况下,依赖关系会过时。这时就需要考虑整体迁移到新的架构。例如:


  • 从 SPARC 迁移到 x86;

  • 从 Solaris 迁移到 Linux。


同样,保持开发人员的战斗力,可对应用做如下更新:


  • 修复顽疾和一些边缘用例。

  • 加固自动测试套件。

  • 清理技术债务。

  • 做性能优化。

  • 实现新特性。

  • 增量重构代码库,改进可读性。


上述变化势必会带来暂时性风险,并产生看似“不必要的”支出。开发人员难免会犯一些错误,甚至引入新的错误。面对这些代价时,人们很容易退而求其次,认为“如果还没有破裂,就不要修复。”


对于一个业务价值不大的系统,这可能是合理的做法。但对于任何关键任务系统而言,忽略问题将仅会掩盖较小的瞬态风险,而没有解决永久的灾难性风险。一旦有一天需要紧急调试或更新系统,那么企业将无所适从。


对于任何关键任务系统,至关重要的就是维持实操知识和能力。做到这一点的唯一方法,就是不断地开展实操。企业的大脑和肌肉一样,不使用,就会失效。


作者介绍:


Rajiv Prabhakar,毕业于密歇根大学和斯坦福大学,之后曾在 Intel 和 Sun Microsystem 公司工作五年,参与了 Jaketown、Skylake 和 SPARC 设计团队。之后转向软件相关工作,先后任职于 Amazon、Google、Engineers Gate,并数次自己组建创业公司。


原文链接:


https://software.rajivprab.com/2020/04/25/preventing-software-rot/


2020-05-09 09:0211335

评论

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

平头哥玄铁处理器Linux新版本,5大亮点速览

Roy夹馍

Linux IoT risc-v 嵌入式开发

为移动通信争一先:Massive MIMO的进化三部曲

脑极体

# 技术栈知识点巩固,开发多年HashMap原理不知道

欢喜学安卓

Java 程序员 后端

2021-07-26 日期时间类,万字总结

欢喜学安卓

Java 程序员 后端

Java 线程池原理分析,一举拿下腾讯美团滴滴offer

JVM调优资料

Java 程序员 后端

Java内存区域总结(堆

Java 程序员

从保证业务不中断,看网关的“前世今生”

华为云开发者联盟

负载均衡 网关 API网关 客户端 API Gateway

一,二,三,基层治理数字化「三步走」

云计算

安卓对RISC-V支持的操作实战

Roy夹馍

安卓 risc-v 嵌入式开发

1年半经验,21条MySQL性能调优经验

欢喜学安卓

Java 程序员 后端

2021最新38道Spring大厂面试题,你碰到过哪道,电商秒杀Java面试题

欢喜学安卓

Java 程序员 后端

Java中高级核心知识全面解析(2),腾讯Java面试

JVM调优资料

Java 程序员 后端

5年Java经验字节社招:半月3次面试,Java资料

欢喜学安卓

Java 程序员 后端

golang--GC(Garbage Collector)垃圾回收

en

GC Go 语言

“眼界大开 声临其境”网易首届音视频技术大会圆满落幕

网易云信

互联网 音视频 网易 网易云信

uni-app技术分享|开源demo视频呼叫arcall uni-app端技术实现

anyRTC开发者

uni-app 音视频 WebRTC 移动开发 视频通话

低代码与专业代码有什么区别?

低代码小观

程序员 开发者 低代码 开发工具 低代码开发平台

Java8-Stream:2万字20个实例,价值2000元的Java学习资源泄露

JVM调优资料

Java 程序员 后端

看懂这个故事,轻松实现从技术到管理的华丽转身!

博文视点Broadview

华为云PB级数据库GaussDB(for Redis)揭秘第一期:Redis与存算分离

华为云数据库小助手

redis GaussDB GaussDB ( for Redis ) 华为云数据库

java-注解,最新Java笔试题分享

JVM调优资料

Java 程序员 后端

Java中高级核心知识全面解析,Java入门教程免费视频

JVM调优资料

Java 程序员 后端

安卓支持RISC-V架构的技术剖析

Roy夹馍

IoT 安卓 risc-v 嵌入式开发

FunTester框架Redis压测预备

FunTester

redis 性能测试 测试框架 FunTester 测试发开

CMake

Changing Lin

9月日更

NFT盲盒玩法详解,盲盒系统开发

合肥艾数199四②43⑧797

2021 Java开发 最全笔记 建议收藏!

欢喜学安卓

Java 程序员 后端

Java 小记 — RabbitMQ 的实践与思考,Redis灵魂14问

JVM调优资料

Java 程序员 后端

Java中高级核心知识全面解析(1),Java开发实战

JVM调优资料

Java 程序员 后端

Java中高级核心知识全面解析(3),Java编程入门到精通

JVM调优资料

Java 程序员 后端

Java反射之Method的invoke方法实现,全栈系统化的学习路线

Java 程序员 后端

程序开发者去世,代码没人懂,一个bug导致千万损失_语言 & 开发_Rajiv Prabhakar_InfoQ精选文章