一行代碼,把項(xiàng)目搞崩潰了!
共 3494字,需瀏覽 7分鐘
·
2024-05-15 13:48
最近我們團(tuán)隊(duì)的同學(xué)在開(kāi)發(fā)中發(fā)生了一件 “有意思” 的事情,那就是通過(guò)一行代碼,讓網(wǎng)站卡死了,真的是離了大譜。團(tuán)隊(duì)同學(xué)寫(xiě)了一篇記錄,希望能長(zhǎng)個(gè)記性吧~
背景
今天下午,老魚(yú)簡(jiǎn)歷告警群里突然提示了幾個(gè)下載簡(jiǎn)歷失敗的提示。
我看到后,心里一緊,趕緊打開(kāi)頁(yè)面看看我能不能下載,結(jié)果我這里下載是正常的。于是我就感覺(jué)事情不簡(jiǎn)單,趕緊本地啟動(dòng)項(xiàng)目調(diào)試,我本以為本地啟動(dòng)后控制臺(tái)會(huì)有報(bào)錯(cuò),但是實(shí)際上并沒(méi)有,沒(méi)辦法,只能使用絕招:二分法先定位到出問(wèn)題代碼。漫長(zhǎng)的縮短問(wèn)題代碼后,最后問(wèn)題代碼找到了
/**
* 判斷是否為鏈接
* @param urlString 接受一個(gè)字符串
* @returns 一個(gè) boolean 值,true 表示是鏈接,false 表示不是鏈接
*/
export const isLink = (urlString: string) => {
const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
return regex.test(urlString);
};
說(shuō)實(shí)話看到是因?yàn)檫@段代碼的我是這樣的:
我本來(lái)是不會(huì)正則的,這個(gè)是 AI 幫我寫(xiě)的判斷鏈接的方法。我當(dāng)時(shí)就光測(cè)試它是否能判斷出一個(gè)字符串是否是鏈接,而忽略了特殊字符串是否會(huì)有性能問(wèn)題。最后,我給大家復(fù)現(xiàn)一下這個(gè)bug 最小代碼示例:
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test('https://www.suse.com/nbswebapp/createSummary.jsp?')
只要你在游覽器控制臺(tái)輸入這個(gè),這個(gè)頁(yè)面立馬卡死。
問(wèn)題原因分析
那這是為什么呢?聽(tīng)聽(tīng)萬(wàn)能的 AI 這么說(shuō)的:在JavaScript中使用正則表達(dá)式時(shí),如果正則表達(dá)式的設(shè)計(jì)不當(dāng),特別是在處理復(fù)雜的字符串或者特定的模式時(shí),可能會(huì)導(dǎo)致性能問(wèn)題,甚至使瀏覽器卡死。這種現(xiàn)象通常稱(chēng)為“災(zāi)難性回溯”(Catastrophic Backtracking)。在你提供的正則表達(dá)式中:
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
這個(gè)表達(dá)式用于匹配URL,其中包含了幾個(gè)可選的組件和量詞(如*和?),這些都可能導(dǎo)致正則表達(dá)式引擎進(jìn)行大量的回溯嘗試,尤其是當(dāng)輸入字符串在某些位置上與模式不匹配時(shí)。具體來(lái)說(shuō),問(wèn)題可能出現(xiàn)在([\/\w \.-]*)*這部分。這里使用了兩個(gè)嵌套的量詞(*外面還有一個(gè)*),這種嵌套的量詞是造成災(zāi)難性回溯的常見(jiàn)原因。當(dāng)輸入的URL在最后一部分(即[\/\w \.-]*應(yīng)該匹配的部分)稍有不符時(shí),正則表達(dá)式引擎可能會(huì)嘗試所有可能的分割方式來(lái)匹配整個(gè)模式,這會(huì)導(dǎo)致處理時(shí)間急劇增加。為了避免這種問(wèn)題,可以嘗試優(yōu)化正則表達(dá)式的結(jié)構(gòu),避免使用嵌套的量詞,或者使用更具體的字符類(lèi)匹配來(lái)減少潛在的回溯。例如,可以修改為:
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]+)?\/?$/
這里,我將內(nèi)部的*修改為+,使得這部分至少匹配一次,減少了嵌套量詞的使用,這應(yīng)該可以幫助減輕瀏覽器的負(fù)擔(dān)。總之,設(shè)計(jì)正則表達(dá)式時(shí)需要注意避免復(fù)雜的嵌套和過(guò)度的通配符使用,特別是在處理可能非常長(zhǎng)或復(fù)雜的字符串時(shí)。這樣不僅可以避免瀏覽器卡死的問(wèn)題,還可以提高應(yīng)用的響應(yīng)速度和用戶(hù)體驗(yàn)。實(shí)測(cè),。這個(gè)正則是有問(wèn)題的:
我現(xiàn)在是用新的檢測(cè)字符串是否是鏈接的方法,充足測(cè)試下來(lái)沒(méi)有問(wèn)題:
/**
* 判斷字符串是否為鏈接
*/
export function isLink(urlString: string): boolean {
const pattern = new RegExp(
'^(https?:\\/\\/)?' + // 協(xié)議
'((([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%_.~+=-]*)?' + // 查詢(xún)參數(shù)
'(\\#[-a-z\\d_]*)?$',
'i',
); // 錨點(diǎn)
return !!pattern.test(urlString);
}
怎么避免?
這次出現(xiàn)這個(gè)問(wèn)題的原因有兩個(gè)
-
經(jīng)驗(yàn)不足:如果我知道,不好的正則出導(dǎo)致災(zāi)難性回溯的話,我在拿到 AI 給我寫(xiě)的正則,我就會(huì)問(wèn)它給我的正則是否會(huì)導(dǎo)致災(zāi)難性回溯。 -
沒(méi)有充足的測(cè)試:如果我的項(xiàng)目有對(duì)這種工具方法的充足的測(cè)試,應(yīng)該也不會(huì)產(chǎn)生這個(gè) bug 了。
總結(jié)
遇到 bug 不要慌,從簡(jiǎn)單到難的使用排查問(wèn)題的方法。先定位到問(wèn)題。例如:我遇到bug,先定位前端問(wèn)題還是后端問(wèn)題,再定位問(wèn)題的大的位置,逐漸縮小范圍,最終找到問(wèn)題的位置。然后解決問(wèn)題。有沒(méi)有覺(jué)得這其實(shí)就是使用二分法的思想來(lái)定位問(wèn)題。找到問(wèn)題的代碼了,那其實(shí)就勝利一大半了,剩下的就是寫(xiě)出正確的代碼,做充足的測(cè)試,最后復(fù)盤(pán)這次 bug,以后不要再犯同樣的錯(cuò)就好了!
???? 點(diǎn)擊下方閱讀原文,獲取魚(yú)皮往期編程干貨。
往期推薦
我的編程學(xué)習(xí)小圈子
我做了個(gè)網(wǎng)站,幫你寫(xiě)出滿(mǎn)分簡(jiǎn)歷
我學(xué)計(jì)算機(jī)的四年,共勉
雙非本,投遞4000+份簡(jiǎn)歷,上岸了!
我開(kāi)源了一套 RPC 框架,學(xué)爆它!
耗時(shí)幾個(gè)月,我們做的小工具上線啦!
