現(xiàn)作為 一名 Go 的開發(fā)者,每天都在與 if err != nil 打交道,或許這已經(jīng)成了 Go 的標(biāo)志性特征。但每當(dāng)遇到 panic 時(shí),程序直接崩潰重啟,讓人皺起眉頭——這個(gè)是不是得用recover處理一下了?
一、go有沒有異常機(jī)制?
其他語言都有成熟的異常處理機(jī)制:Java 的 try-catch,Python 的 except,JavaScript 的 try-catch。而 Go 呢?它說:「我們不需要異常,錯(cuò)誤也是值!」
結(jié)果卻偷偷提供了 panic 和 recover:
- panic 會立刻中斷當(dāng)前函數(shù)的正常執(zhí)行,沿著調(diào)用棧向上回溯,依次執(zhí)行已注冊的 defer
- recover 在 panic 向上傳播過程中截獲它,讓程序恢復(fù)正常執(zhí)行
- 如果沒有 recover,程序會崩潰退出
// 日常錯(cuò)誤處理:無處不在的 if err != nil
func readFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
// 更多 if err != nil...
}
// 但偶爾又會看到這種「異類」
func riskyOperation() {
defer func() {
if r := recover(); r != nil {
log.Println("程序差點(diǎn)崩了:", r)
}
}()
// 某些可能會 panic 的操作
panic("Oops!")
}
二、Web 應(yīng)用panic噩夢
在 Web 開發(fā)中,一個(gè)未 recover 的 panic 就意味著整個(gè)服務(wù)進(jìn)程崩潰!你知道在生產(chǎn)環(huán)境看到「502 Bad Gateway」是因?yàn)橐粋€(gè)沒捕獲的 panic 時(shí)有多絕望嗎?
func main() {
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
// 萬一這里 panic 了...
panic("數(shù)據(jù)庫連接失敗!")
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
于是我們不得不在每個(gè) handler 里寫重復(fù)的 recover 代碼,不想讓服務(wù)直接崩潰~
func safeHandler(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("捕獲到 panic: %v", r)
http.Error(w, "內(nèi)部服務(wù)器錯(cuò)誤", http.StatusInternalServerError)
}
}()
h(w, r)
}
}
三、panic/recover 到底該用在什么地方?
在大多數(shù)情況下,清晰的錯(cuò)誤返回值比隱式的 panic/recover更符合 Go 的哲學(xué)。只有在真正異常的情況下,才應(yīng)該考慮使用 panic/recover。
- 真正不可恢復(fù)的錯(cuò)誤:比如程序啟動(dòng)時(shí)配置檢查失敗
- 防止程序崩潰:在頂層捕獲 panic,保證服務(wù)不會因?yàn)橐粋€(gè)請求而完全宕機(jī)
- 復(fù)雜嵌套的退出:在某些深層遞歸或復(fù)雜操作中,panic 可以作為一種「快速退出」機(jī)制