当前位置: 欣欣网 > 码农

一行代码,把项目搞崩溃了!

2024-05-15码农

最近我们团队的同学在开发中发生了一件 「有意思」 的事情,那就是通过一行代码,让网站卡死了,真的是离了大谱。团队同学写了一篇记录,希望能长个记性吧~

背景

今天下午,老鱼简历告警群里突然提示了几个下载简历失败的提示。


我看到后,心里一紧,赶紧打开页面看看我能不能下载,结果我这里下载是正常的。于是我就感觉事情不简单,赶紧本地启动项目调试,我本以为本地启动后控制台会有报错,但是实际上并没有,没办法,只能使用绝招: 二分法 先定位到出问题代码。漫长的缩短问题代码后,最后问题代码找到了

/**
 * 判断是否为链接
 * @param urlString 接受一个字符串
 * @returns 一个 boolean 值,true 表示是链接,false 表示不是链接
 */

exportconst isLink = (urlString: string) => {
const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
return regex.test(urlString);
};


说实话看到是因为这段代码的我是这样的:

我本来是不会正则的,这个是 AI 帮我写的判断链接的方法。我当时就光测试它是否能判断出一个字符串是否是链接,而忽略了特殊字符串是否会有性能问题。最后,我给大家复现一下这个bug 最小代码示例:

/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test('https://www.suse.com/nbswebapp/createSummary.jsp?')


只要你在游览器控制台输入这个,这个页面立马卡死。

问题原因分析

那这是为什么呢?听听万能的 AI 这么说的:在JavaScript中使用正则表达式时,如果正则表达式的设计不当,特别是在处理复杂的字符串或者特定的模式时,可能会导致性能问题,甚至使浏览器卡死。这种现象通常称为「 灾难性回溯 」(Catastrophic Backtracking)。在你提供的正则表达式中:

/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/


这个表达式用于匹配URL,其中包含了几个可选的组件和量词(如 * ? ),这些都可能导致正则表达式引擎进行大量的回溯尝试,尤其是当输入字符串在某些位置上与模式不匹配时。具体来说,问题可能出现在 ([\/\w \.-]*)* 这部分。这里使用了两个嵌套的量词( * 外面还有一个 * ),这种嵌套的量词是造成灾难性回溯的常见原因。当输入的URL在最后一部分(即 [\/\w \.-]* 应该匹配的部分)稍有不符时,正则表达式引擎可能会尝试所有可能的分割方式来匹配整个模式,这会导致处理时间急剧增加。为了避免这种问题,可以尝试优化正则表达式的结构,避免使用嵌套的量词,或者使用更具体的字符类匹配来减少潜在的回溯。例如,可以修改为:

/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]+)?\/?$/


这里,我将内部的 * 修改为 + ,使得这部分至少匹配一次,减少了嵌套量词的使用,这应该可以帮助减轻浏览器的负担。总之,设计正则表达式时需要注意避免复杂的嵌套和过度的通配符使用,特别是在处理可能非常长或复杂的字符串时。这样不仅可以避免浏览器卡死的问题,还可以提高应用的响应速度和用户体验。实测,。这个正则是有问题的:

我现在是用新的检测字符串是否是链接的方法,充足测试下来没有问题:

/**
 * 判断字符串是否为链接
 */

exportfunctionisLink(urlString: string): boolean{
const pattern = newRegExp(
'^(https?:\\/\\/)?' + // 协议
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // 域名
'((\\d{1,3}\\.){3}\\d{1,3}))' + // 或IP(v4)地址
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // 端口和路径
'(\\?[;&a-z\\d%_.~+=-]*)?' + // 查询参数
'(\\#[-a-z\\d_]*)?$',
'i',
); // 锚点
return !!pattern.test(urlString);
}

怎么避免?

这次出现这个问题的原因有两个

  1. 经验不足 :如果我知道,不好的正则出导致 灾难性回溯 的话,我在拿到 AI 给我写的正则,我就会问它给我的正则是否会导致 灾难性回溯

  2. 没有充足的测试 :如果我的项目有对这种工具方法的充足的测试,应该也不会产生这个 bug 了。

总结

遇到 bug 不要慌,从简单到难的使用排查问题的方法。先定位到问题。例如:我遇到bug,先定位前端问题还是后端问题,再定位问题的大的位置,逐渐缩小范围,最终找到问题的位置。然后解决问题。有没有觉得这其实就是使用二分法的思想来定位问题。找到问题的代码了,那其实就胜利一大半了,剩下的就是写出正确的代码,做充足的测试,最后复盘这次 bug,以后不要再犯同样的错就好了!


👇🏻 点击下方阅读原文,获取鱼皮往期编程干货。

往期推荐