hello~亲爱的看官老爷们大家好~最近接手维护公司另一个 Node
项目,稍微熟悉一下代码后,便被提了一个解决线上 bug 的需求。定位问题后解决还是十分容易的,但是这个过程十分有趣,bug 出现的原因也值得深思。因而有了这篇文章,分享这次 bug fixes 的过程。
背景
项目是搜索相关的,页端发请求到 Node
端,Node
包装搜索请求参数后转发 Java
,Java
返回数据后,Node
将数据组装成 HTML
文档返回给页端。整个过程非常常规,但 Bug 出现的位置就十分奇妙了,线上偶现渲染出来的 HTML
文档中部分 a
标签链接指向了错误但有意义的地址。简单的代码如下:
const axios = require('axios');
const _arr = [
//数组内是一个个对象,保存着一些公用的数据
];
router.get('/test', (req, res) => {
axios(
//请求 Java 的各种配置
)
.then(response => {
//对 response 进行处理,省略若干步骤
for (let item of _arr) {
item.url = `?${querystring.stringify(response.url)}`;
}
})
.then(() => {
//其他的异步操作
})
.then(data => {
res.render({
//...各种数据
_arr
})
})
})
module.exports = router;
如果对 Node
模块机制比较熟悉的同学,应该能看出问题的根源了。但如果是接手的项目,而且实际代码比这个复杂 N 倍(项目中此接口大约有700行代码),估计就没那么好分析了。
问题分析
当时我接手时,在本地环境和测试环境,无法复现出相同的问题。同时,被修改的链接其实是重新搜索的链接,如之前搜索的是 123
,跳转的链接正常来说还是 123
的,但会变为 xxx风景区
。因而一度认为是有中间人进行攻击,修改了模板中的链接。但分析的过程中,发现两个不合理的地方:
- 如果真的是中间人攻击,那么为何不修改全部的链接?
- 搜索页面支持
Https
,修改修改协议后,线上环境仍偶现该问题。
如果说第一点还情有可原的话,第二点基本就认定不是中间人的问题,除非中间人强到破解了加密或公司的私钥泄密了。于是问题又回到了原点,代码到底哪里又出问题了呢?
线上环境是较难调试的,但可以通过打日志的方式进行分析。通过打点,发现请求 Java
后返回的 response.url
是正确的,但在渲染时 _arr
的 url
却不同了,可以确定问题是出在 _arr
上,因为某些原因,它的值被修改了。
定位到具体出现问题的地方后,原因归结起来就很简单了,Node
中的模块机制,其实是通过函数包装而成的,在此过程中会形成闭包,简略示例如下:
const module = {}; //全局变量
function(exports, require, module, __fileName, __dirname) {
const _arr = [];
module.exports = ....;
}
每个文件都会被加工成上面代码的形式,而 module
是全局可访问的。而且需要记住一点,不是每次用到某个文件时,函数都会执行一次,而是只要该文件被 require
一次后,它的相关代码已经被记录在全局的 module
中了。因而 _arr
是以单例的形式存在的,如果 exports
出去的代码对 _arr
做了修改的话,会影响此后对 _arr
的读取。好像有点绕,再简单一点说,如一下 demo
:
function wrap() {
const arr = [];
return function() {
console.log(arr);
arr.push(1);
}
}
const module = wrap();
module(); //[]
module(); //[1]
module(); //[1, 1]
而项目的代码有这么一段:
for (let item of _arr) {
item.url = `?${querystring.stringify(response.url)}`;
}
这段代码是有副作用的,它修改了 _arr
的内容,如果之后都是同步的代码,那么问题可能还不大(其实还是有问题,但不会影响结果),但只要是异步的,那么问题可能就会出现。知道了问题的根源,解决就很简单了,要么每次调用都重新定义 _arr
一次,要么将代码改为无副作用的~
小结
以上就是整个 debug 过程的简要描述,其实只是之前开发的同学某些细节没有考虑到,还好访问量不大,但造成的后果不算特别严重。然而分析下来,发现这个 bug 是若干 Js
基础问题共同作用而成的,当我们不断追求新技术、新框架的同时,是不是该静下心来,好好巩固一下语言的基础呢?
感谢各位看官大人看到这里,知易行难,希望本文对你有所帮助~谢谢!