最近業(yè)余時(shí)間在閱讀《代碼大全》,閱讀“防御式編程”章節(jié)的時(shí)候非常受啟發(fā),自己之前對(duì)系統(tǒng)的錯(cuò)誤處理這塊也確實(shí)隨意了。
什么時(shí)候應(yīng)該代碼應(yīng)該自己catch掉錯(cuò)誤并且處理了,什么時(shí)候應(yīng)該拋出給上層,拋到上層時(shí)候要如何處理,如何使用斷言等等沒(méi)有一套規(guī)范。所以這里總結(jié)下應(yīng)該如何來(lái)做防御式編程。
什么是防御式編程
防御式編程是承認(rèn)程序都會(huì)有問(wèn)題,無(wú)論是自己寫的模塊,團(tuán)隊(duì)中其他人寫的模塊甚至第三方工具包。然后依據(jù)這個(gè)來(lái)指導(dǎo)寫代碼,子程序不應(yīng)該因?yàn)閭魅氲腻e(cuò)誤數(shù)據(jù)被破壞了。
為什么需要防御式編程
因?yàn)榉婪犊此莆⑿〉腻e(cuò)誤,收獲可能大到你的想象。
最簡(jiǎn)單的例子就是對(duì)輸入的參數(shù)做校驗(yàn),比如刪除一個(gè)數(shù)據(jù)的接口需要對(duì)輸入?yún)?shù)做校驗(yàn)防止產(chǎn)生全匹配從而刪除全部數(shù)據(jù)。
如何來(lái)做
一般會(huì)結(jié)合使用斷言(asset)和錯(cuò)誤捕獲及處理(try...catch)。
斷言對(duì)于前端開發(fā)來(lái)說(shuō)可能有些陌生,斷言的語(yǔ)法是assert(equal, message),只要equal為假,那么message會(huì)被記錄下來(lái)(對(duì)于前端來(lái)說(shuō)可能是寫到console中,對(duì)于后端來(lái)說(shuō)可能是寫到日志文件)。
兩者區(qū)別在那里呢:斷言來(lái)處理絕對(duì)不應(yīng)該發(fā)生的狀況(如果發(fā)生了,說(shuō)明代碼存在bug),錯(cuò)誤處理來(lái)處理預(yù)期會(huì)發(fā)生的狀況,或者說(shuō)斷言用來(lái)檢查代碼的bug,錯(cuò)誤處理用來(lái)檢查有害的輸入數(shù)據(jù)。
斷言更多是在開發(fā)中使用的,可以更好的幫助我們?cè)陂_發(fā)過(guò)程中定位錯(cuò)誤(如果console中出現(xiàn)了斷言的記錄,我們可以順著調(diào)用棧找到出錯(cuò)的源頭,這比自己猜測(cè)然后加斷點(diǎn)調(diào)試要快一些)。如果不是對(duì)性能有太大的影響的話我個(gè)人建議可以在生產(chǎn)環(huán)境也打開斷言記錄到sentry之類的日志服務(wù)中(需要注意信息的加密),這樣可以更好的幫助我們定位bug。
比如從一個(gè)用戶列表中刪除一個(gè)用戶的函數(shù)deleteUser(userId),可以使用斷言來(lái)判斷這個(gè)用戶是不是在列表中,如果沒(méi)在列表中說(shuō)明肯定是代碼出現(xiàn)了bug了(比如重復(fù)刪除同一個(gè)用戶或者這個(gè)函數(shù)的調(diào)用者傳參數(shù)存在問(wèn)題):
class User {
constructor() {
this.userList = [];
}
deleteUser(userId) {
const isExitUser = this.userList.findIndex(user => user.id === userId) !== -1;
asset(isExitUser, `${userId}不在用戶列表中`);
}
}
錯(cuò)誤處理來(lái)應(yīng)對(duì)預(yù)期會(huì)發(fā)生的狀況,比如客戶端請(qǐng)求了一個(gè)api接口,正常情況下這個(gè)接口返回200,但還是存在極少數(shù)的情況下這個(gè)接口會(huì)返回500,我們可以捕捉到這個(gè)錯(cuò)誤來(lái)做一些處理,比如重新發(fā)起一次請(qǐng)求之類的。
fetchUserList().then((res) => {
storeUserInfo(res.data);
}).catch((e) => {
if (e.message = '500') {
// TODO 這個(gè)函數(shù)還要注意下調(diào)用棧的深度,最多重復(fù)執(zhí)行四次,防止調(diào)用棧過(guò)深導(dǎo)致爆掉
fetchUserList();
}
})
異常使用有這么幾個(gè)原則:
如果異常可以在局部處理就在局部處理,不要放到外面去。
千萬(wàn)不要只捕獲異常卻什么都不做,比如
fertchUserList().catch(() => {}),catch函數(shù)里面沒(méi)有做任何處理。拋出的異常的消息中加入異常發(fā)生的相關(guān)信息,比如如果在刪除一個(gè)用戶的時(shí)候,因?yàn)橛脩舨辉诹斜碇形覓伋隽艘粋€(gè)異常,那么異常就要寫明白,要?jiǎng)h除的用戶的id是什么以及其他環(huán)境信息,這樣可以幫助我們排查問(wèn)題。
寫代碼的時(shí)候一定要去考慮要各種異常的情況并做好穩(wěn)妥的處理,這樣才能寫出更加魯棒性高并且易于debug的代碼。