现象
在我们公司的一个维护了20多年的项目里,发现只要引用第三方组件库或者方法库,都会报错。无论是把bootstrap v3换成v5,还是引用vue.js。由此,我意识到这个项目维护了20多年,几乎很少有新的库的引用,一直在用十几年前/20多年前的插件,原因可能就在这里。我还联想到《三体》小说里的智子封锁地球科技的情节,这个bug可能就是让开发者直呼"新技术不存在了"的智子了。
智子锁死的并非是人类的全部科技,智子锁死的人类基础科学是基于以下认知:科学技术的全面发展取决于基础科学的发展,而基础科学的基础又在于对物质深层结构的探索,如果这个领域没有进展,科学技术整体上就不可能产生重大突破。即将锁死基础科学就等于堵塞该星球提升文明等级的道路。
原因分析
看报错信息,都是指向了filter方法。看已有的代码,我发现filter似乎和正常的用法不一样。
我直接在浏览器上输入了[].filter,发现打印出来的不是【native code】,而是这样的:

点击可以进入filter所在的代码:

至此:我们找到问题点了:由于项目年代十分久远,在数组的filter等方法出来之前,有人在网上找了一些方法,是数组方法的扩展,其中包括了filter,并且将其写到数组的prototype上。
js
var extendArr = {
filter: function(f, g) {
var e = g || window;
var b = [];
for (var d = 0, c = this.length; d < c; ++d) {
if (!f.call(e, d, this[d], this)) {
continue
}
b.push(this[d])
}
return b
},
// 还有很多其他的数组方法,不一一列举了
};
for (var method in extendArr) {
Array.prototype[method] = extendArr[method]
}
从if (!f.call(e, d, this[d], this))这一行看,我们发现d是在this[d]前面,也就是说,这个filter方法的value和index是颠倒的。
运行效果:
js
var arr = ['a', 'b']
arr.filter(v => v == 'a')
// 运行结果是 []
arr.filter((i, v) => v == 'a')
// 运行结果是 ['a']
下图是bootstrap v5的打包文件,在我们的环境下,572行执行的时候直接报错。

后果
正如《三体》中的智子干扰物理实验,使得地球的科技停滞不前一样,这一段代码让我们这个庞大的项目永远无法引用新技术。
在我们这个项目的环境下,关于filter的代码全部都是有问题的。因此我们想引用第三方组件库,只要源码里用到了filter方法,全部无法正常使用。(这里列举出来的只有filter方法,实际上,some、every这样的方法都被魔改过)。
动弹不得的现状
初步搜索后,发现有几百个地方用到了filter方法,甚至一些关联的公共组件库也用到了。如果删掉这段代码,会导致我们的系统瘫痪。
最终我们只能走两条路:
- 在现有的架构上继续开发,放弃第三方组件库;
- 对于新的页面,我们放弃通用的架构,
jqGrid等插件都不引用;
第一种方式,就是【闭门造车】,第二种情况,要求员工走出舒适区,不再Ctrl CV已有的代码。
启示与思考
- 避免出现破坏底层的改动,否则后续产生的技术债难以想象。
另一个离谱的架构设计是这样的:UI提供了三个字体文件,分别是font-bold.woff,font-regular.woff,font-thin.woff,这样切换字体的粗细就不是用font-weight了,而是用font-family。结果所有的第三方UI库的加粗功能全部与这个设定冲突。
这也是魔改了最基础的底层逻辑:不再使用font-weight来设置字体粗细。改了这个底层逻辑后,所有依赖font-weight的库都将受到影响。
- 公司是否考虑定期的架构维护
很多公司的技术架构,都是非必要不升级的,因为公司都是业务导向的,只要这个功能可以用,并且有客户正在使用,大概率是不会花精力重构的。而新业务的开发,也大概率沿用已有的技术架构。
所以真正的【智子】不仅仅是破坏了底层的代码,而是公司对于技术、对于员工的态度。如果公司认为技术革新不重要,只要这些老项目有员工扛着就万事大吉,那么技术的封锁就已经形成。