作者 Jake A. Smith 讲了一个很典型的故事。2014 年,团队把 AOL 的内容管理系统从 PHP 5.2 升到 5.3,同时要放弃 pecl_http 的 1.x 版本,而系统里有几十处都在调用它提供的 http_build_url()。

蓝色金属面上锈蚀的螺栓与螺母特写

174 行,一用就是十二年

他不想动那些老代码,于是照着重写了一个同名函数,只在原函数不存在时才定义,让老代码完全察觉不到变化。为了让别人也省事,他把这 174 行放上了 Packagist。他的预期是“顶一两年”。

结果是从 Packagist 被安装接近两千万次,如今每月仍有四十多万次安装。

它比想象中扩散得远得多

Composer 只是一部分。WordPress 的多语言插件 WPML 直接把这套兼容代码打包进了自己的代码库,而 WPML 自称装在超过一百五十万个站点上;域名处理库 idna-convert 依赖它,于是它又进了法国 CMS 项目 SPIP 的源码,并被收进 Debian 与 Ubuntu 的软件包。按作者的推算,你访问过的某个网站,很可能还在跑他这段代码。

更值得看的是它藏着的缺陷。有议题反馈:把路径拼到带结尾斜杠的 URL 上,路径里所有字母 a 都会消失。原因就写在注释里——为了绕开结尾斜杠,代码给路径补一个字母 a 当作“可切的最后一段”,再用查找替换切掉;当路径本身以斜杠结尾时,最后一段就是那个 a,查找替换顺手把路径里其余所有的 a 一起删了。

临时方案的寿命总被低估

这不是一个人的疏忽,而是一类系统性偏差:我们评估“临时方案”时默认它会被替换,却不给它被替换的条件。没有测试、没有文档、没有明确的负责人和失效日期,唯一确定的是它会一直被人依赖。

从工程实践看,有三件事值得写进流程:其一,临时补丁也要有测试,尤其是边界输入,比如带结尾斜杠的路径;其二,在说明文档顶部写明“何时该删掉我、由谁决定”,把下架路径写清楚;其三,交接时把这类依赖点名列出,别让作者离开之后就没人接手——作者本人也提到,2021 年他想过找人接手,后来因家庭变故没能跟进。

十二年后,他把这个包标记为废弃。启示并不复杂:判断一段代码值不值得认真对待,不该看它当初写得多临时,而该看现在有多少东西压在它上面。