当我们将时间浪费在谈论大型机时,殊不知技术衰败的威胁已经迫在眉睫。
每当我接到采访请求,或者被邀请谈论我在遗留技术现代化方面的工作时,每个人都想谈论大型机和 COBOL。人们认为,我会给其他工程师讲一些有趣的战争故事,关于老旧系统的苦差事,这些工程师无须担心这些事情,因为他们的职业专注于现代技术。
当然,当我开始处理遗留系统时,我也被最古老的节目李普利的《信不信由你》 (Ripley's Believe It or Not!)(译注:1921 年由李普利推出)给吸引住了。挖掘和剖析老旧系统的快感,发现大多数程序员从未听说过的、被遗忘的语言,更不用说与之交互了。我一直着迷于低级语言和系统,它们将电压变化转化成数学和设计中的抽象概念,这是一种什么样的魔法啊!但最近,我对即将到来的遗留启示录,以及如何减缓新技术带来的技术债务不断上升的水平更感兴趣了。
遗留启示录并不是婴儿潮一代最后一个 COBOL 程序员的离世。说实话,这场危机来得快,走得也快。当人们谈论老旧系统的威胁时,他们喜欢搬出有关 COBOL 程序员年龄的统计数据来说事。例如,在 2006 年,COBOL 程序员平均年龄为 55 岁。这听起来很糟糕啊!许多关键员工都快退休了!他们要是离开了以后,谁来照看他们留下的系统呢?
平均值可能会误导公众。在同一调查中,有 52% 的程序员年龄在 45~55 岁之间,34% 在 35~45 岁之间。但更重要的是,8 年以后,当那些 55 岁的程序员都应该退休的时候,Micro Focus 对 COBOL 程序员和高管的调查将 COBOL 程序员的平均年龄再次定为 55 岁。他们在 2019 年的调查中得出的平均值就是 50 岁。
实际上,几十年来,COBOL 程序员的平均年龄就一直保持稳定。当我父亲研究千年虫问题时,当时他都已经 40 多岁了……快 50 出头。他的同事年龄与他相仿。每次我看到人们在 COBOL 社区的年龄上大做文章的时候,我就会想起美国双簧管演奏家 Blair Tindell 写的关于古典音乐界的文章:
对听众老龄化的恐慌大可不必。因为我们忽略了一个事实:其实听众平均年龄在 40 岁左右已徘徊了一段时间。人们等到中年才开始欣赏交响乐,是合乎逻辑的。随着孩子长大,学费付完,人们才有了更多的闲暇时间,音乐会很符合成熟后的婴儿潮一代丰富的生活方式、品味和收入。
关于 COBOL 也可以有类似的说法。与 60 年代、70 年代和 80 年代的年轻程序员不同,今天的年轻程序员上大学的时候没有接触过大型机。如果大学里还有大型机的话,那就是行政部门的主力机器,对于学生项目来说太重要了。年轻程序员也没有学习 COBOL 的选择。就算他们做了数百个(或者有些人说是数千个)COBOL 工作,也不是入门级的。
COBOL 程序员的平均年龄之所以稳定,很可能是因为 COBOL 程序员在职业生涯后期转向 COBOL 之前,已经在其他语言方面积累了丰富的经验和专业知识。
人们总是担心那些上了岁数的 COBOL 程序员,因为他们认为,当最后一批 COBOL 程序员离世后,他们开发的程序将无人能够维护。人们有这种担心是情有可原的。但是,大多人会惊讶地发现,无法维护的遗留代码带来的威胁比他们想象的要近得多。
164% 的 Java 应用程序停留在 Java 8 上
如果你还记得的话,Java 的最新版本是 14。Java 8 应该在 2019 年就停止支持了。
Java 9 引入了结构上的一些变化,使 Java 更加模块化,因此 Java 9 对嵌入式系统来说更加可行。从 Java 8 转到 Java 9 并不是升级,而是全面迁移。其中,Java 9 使 JDK 内部的 API 无法访问,它剔除了一些工具和方法,而且向模块化结构的转变需要更改依赖关系。换句话说,从 Java 8 迁移到 Java 9 有可能意味着很多代码必须重写。
因此,Snyk 在 2020 年进行的关于产品应用程序的调查中,发现超过一半应用仍然运行在 Java 8 之上。
2Python 2
和 Java 8 一样,Python 2 也一直挥之不去,因为迁移到 Python 3 既要重写自己的代码,又要从所有依赖关系中移除 Python 2。虽然有像 Benjamin Peterson 的 Six 这样的工具可以使任务变得更愉快,但依赖关系可并不仅仅是包和库。代码运行的平台也是一个依赖关系,而且这些平台的响应速度很慢。尽管 Python 是一个极其流行的脚本工具,但 AWS Lambda 直到 2017 年才支持 Python 3.6,而这一支持也是 Python 3.6 发布一年之后的事。同年 Salt 才提供 Python 3 的支持。一年后,Ansible 也支持了 Python 3,但此时已经是 Python 3 最初发布的十年之后了。
很难说世界上还剩下多少人在使用 Python 2。据 JetBrains 估计,这个数字只有 10%,而且这个调查是来自 150 个不同国家的 2.4 万名受访者,因此这一数字可能是准确的。Python 2 的问题不在于还在使用的应用有多少,而在于它仍用于哪些地方。根据 JetBrains 的说法,Python 2 与 Python 3 仍存在竞争关系的领域是 DevOps/ 自动化、测试和网络编程。事实证明,要让不同风格的 Linux 完全支持 Python 3,是一个巨大的挑战。这场战争还没有结束,每个热爱 Mac 的 Python 爱好者都知道,由于 MacOS 内部工具的缘故,Apple 计算机在出厂时仍然以 Python 2.7 为默认的 Python 版本。
3人人皆烦 jQuery,但它无处不在
依赖地狱的另一层是 jQuery。只是从 jQuery 迁移出来并不难,但很多其他东西都依赖于 jQuery,依赖关系会让迁移变得很困难。
2019 年,Twitter Bootstrap 最终将 jQuery 从依赖项中移出,只是因为他们直接将 jQuery 的源码复制粘贴到 Bootstrap 中。即便如此,整个项目从开始到结束,也耗费了两年多的时间。
jQuery 是自身成功的牺牲品。它简单的语法如此流行,以至于其他框架,甚至连原生 JS 都开始采用它。最重要的是,许多 jQuery 提供交叉兼容的遗留技术最终都退役了(看看你的 IE 浏览器)。我个人认为,围绕 jQuery 的担忧有些夸张,但我不是 JavaScript 专家。这场反 jQuery 运动似乎是由于框架和当前流行的 MVC JavaScript 框架 React 之间的冲突而拉开序幕的。
但是,就像所有的技术圣战一样,反对选择一个选项而不是另一个选项的合理论据,重复的次数越多,就越模糊不清。在某些方面,我认为 jQuery 的故事与 COBOL 的故事最为相似,因为有关它的头条新闻无处不在,暗示着因为其他技术也可以做同样的事情,所以其他(更新的)技术必定更好。
4深度,而非年龄
遗留系统难以维护的原因有很多,负责维护的程序员的年龄并不是原因之一。诚然,机构记忆的丢失很重要,当最了解系统的程序员离开时,机构记忆也会随之而去。但这并不是老旧技术独有的问题。组织也有因员工被挖墙脚而失去机构记忆的情况,就像他们因员工退休而失去机构记忆的情况一样(可能还要多)。
译注:机构记忆(institutional memory),看字面似乎不太易懂。它的意思是机构的集体性经验结晶,是指整个机构内所有员工的总体经验或记忆结晶,轻易解雇老员工就会丧失“机构记忆”。因此,机构记忆也可以认为是机构的经验传承制度化记忆。
确实,精通 COBOL 的工程师人才库是有限的,但通过建立培养 COBOL 人才的管道,解决这个问题花不了多少钱,也不难。IBM 在这一领域一直非常活跃,他们有“大型机大师”(Master the Mainframe)计划。COBOL 程序员是一种正在枯竭的有限资源?没有的事。
我不得不说,以我的经验来看,每当一个 COBOL 系统出现故障时,几乎从来都不是 COBOL 本身引起的。我见过硬件故障、支持或以其他方式与 COBOL 集成的非 COBOL 系统引起的问题,我还见过因为 COBOL 代码的文档不完善而导致新特性的添加延迟,工程师需要找出更改的方法……但我还没有见过很多系统使用 COBOL 这一事实本身就是问题的情况。这并不是说,放弃 COBOL 没有很好的理由,理由肯定有。我只是不倾向于同意公民社会不能在接下来的 60 年里继续运行数百万行 COBOL 代码。要继续运行?当然可以。
另一方面,Java 8 和 Python 2 才是更严重的威胁。当系统无法摆脱产品寿命结束(End-of-life,EOL)的技术时,它们就会错过安全更新、性能增强和新特性。系统停留在自己的技术债务上的时间越长,建立在它们之上的东西就越多,遗留的东西就越更加根深蒂固。
当我们表现得好像关于遗留代码日益增长的威胁的对话以 COBOL 开始和结束时,对于程序员是一种伤害。整整一代软件工程师都在把他们的应用程序除了最独特的方面之外所有的方面,都外包给了大量的库、插件和模块,而这些库、插件和模块他们根本无力监控,更不用说更新了,这才让问题变得更糟糕。
遗留启示录中真正的骑士是依赖树的深度。现代软件开发将抽象堆叠在抽象之上。如果说 2016 年的“left-pad”事件没有证明什么的话,那么它至少证明了这一点:即使是有经验的工程师也会在他们的应用程序中使用依赖关系,如果有基础设施可以使它们的安装变得容易的话。现代开发人员环境就是一个名副其实的、廉价而方便的依赖关系的“糖果店”。
5框架的兴起
如果维基百科(Wikipedia)可以被认为是一个权威的来源,那么围绕开发全新编程语言的活动在 20 世纪 90 年代达到了顶峰,当时很多人都可以使用计算机,但抽象程度仍然相对较低。直到互联网的出现,改变了这一局面,不仅使更复杂的分布式系统成为现实,而且还扩大了安全问题的爆炸半径。人们对更好的性能和更好的安全性的需求,使得在现代机器上使用新语言的 MVP 相当复杂。聪明的计算机科学家再也不能建立概念验证不成熟的语言,并期望将它们应用于现实世界的问题,以推动它们的进化。编程语言需要为程序员处理大量复杂的任务。
因此,尽管专业程序员的数量自 20 世纪 90 年代的辉煌时期以来急剧增长,但这些软件专家已经从开发新语言转向开发新框架。
而从本质上讲,框架不过是一个给定了通用接口的依赖关系的集合。诚然,框架能够使软件开发速度更快,但它们也剥夺了开发人员维护代码的能力。工具的进步降低了软件开发的速度,不可避免地加深了一般软件项目的依赖树。
以 Node.js 为例,Node 是一个有趣的框架,它使得在服务器端运行 JavaScript 成为可能,但它也引入了一个名为 NPM 的精巧的小包管理器(作为一个依赖项)。以前也有过包管理器,NPM 未必是最好的,但它从之前的包管理器中吸取了一些教训,提供了更好的用户体验。默认情况下,它是在本地而不是全局安装的。命令行从一开始就被设计成与包仓库集成,所以创建和发布新包是非常容易的。
因此,NPM 上依赖树的平均深度是 4.39 个包,而同类包管理器(这里以 PyPi 为例)的平均深度是 1.7 个。Python 开发者并不是天生就比 JavaScript 开发者更负责任。JavaScript 缺乏一个好的核心库,而且它曾经是一个玩具语言,设计和实现都是在一个星期内完成的,这使得开发框架来平滑它的粗糙边缘的时机已经成熟。有很多 NPM 包做一些小的、“愚蠢的”事情,而这些事情在其他语言中是以内置函数的形式出现的。NPM 使分享变得很容易。
包依赖关系对比:NPM 与 PyPi。真是可怕的对比。
但如果 ECMA 决定像 Java 9 和 Python 3 试图解决其语言的结构性问题那样,修复 JavaScript 的一些缺点,会发生什么呢?NPM 上大约有 60% 的软件包已经有一年或更长时间没有更新了。尽管缺乏维护,但这些包仍然被下载了数十亿次。
ECMA 在他们的“One JavaScript”政策中承认了这一事实:
但我们如何才能摆脱版本控制呢?通过始终保持向后兼容。这意味着我们必须放弃一些清理 JavaScript 的雄心壮志。我们不能引入破坏性的变化。向后兼容意味着不移除特性,也不改变特性。这一原则的口号是“不要破坏 Web”。
我们可以针对永远向后兼容的好处争论一整天。但问题是,随着 JavaScript 框架的普及,JavaScript 一直以来固有的、巨大的依赖路径已经变得无限糟糕。因此,同样的工具在解决像 JavaScript 这样的语言的大量结构性问题的同时,也使得这些问题无法在新版本的 JavaScript 中得到解决。
当我们谈到长期维护健康和安全的技术系统时,这比 COBOL 程序员时代的威胁要大得多。然而,当我们谈论遗留技术时,并没有谈论这些问题。
6总结:战略高于速度
依赖关系是一种必要的“邪恶”,但使用依赖关系,并不一定要让项目堕入遗留的地狱。我们需要开始将长期维护目标纳入关于技术选择的谈话中。JavaScript 框架创建了深度依赖树,是的,但是即使 NPM 是为了满足后端语言的需要而开发的,其上的 80% 的活动都与前端有关。设计界普遍认为,网站大概每两三年就会重新设计一次。所以,从遗留技术现代化的角度来看,拥有大型依赖关系图的 React 前端与拥有同样大小的依赖关系图的 Node 应用程序相比,不那么需要担心。
换句话说,我们需要开始批判性地思考我们期望某项技术能持续多久,并扪心自问,我们在构建它时所做的选择,是否会使它在以后更难被移除。我们再也不能坐等更好的结果出现。我们必须假设的是,更好的结果终将出现。
最后,我们需要重新聚焦话题,不要再因为技术老旧、是由老人编程就将技术妖魔化。世界上有很大一部分的 COBOL 应用程序,在 COBOL 上表现很好。确实存在的问题也可以在 2002 年构建的 Webapp 中找到。COBOL 是上古语言这一事实无关紧要,而且还分散了人们对日益增长的代码生态系统的注意力,尽管它的产品生命周期早已结束。