誤處理))
錯(cuò)誤處理Go沒有try / catch。日常約定是能預(yù)期的失敗參數(shù)不對、查無此人、除數(shù)為 0→ 返回error由調(diào)用方?jīng)Q定怎么處理。不該發(fā)生、程序狀態(tài)已不可信下標(biāo)越界、斷言失敗、嚴(yán)重邏輯崩壞→ 用panic必要時(shí)再在上層用deferrecover兜底。本文用「九九乘法表」同一個(gè)業(yè)務(wù)場景對比這兩種寫法的差別。業(yè)務(wù)錯(cuò)誤 vs 程序錯(cuò)誤類型常見手段出錯(cuò)后同函數(shù)里后面的代碼典型場景業(yè)務(wù)錯(cuò)誤返回(結(jié)果, error)還能繼續(xù)走只要你沒return用戶輸入非法、業(yè)務(wù)校驗(yàn)失敗程序錯(cuò)誤panic不再執(zhí)行即使recover了也是如此嚴(yán)重異常、不可恢復(fù)狀態(tài)記住口訣業(yè)務(wù)錯(cuò)誤是「報(bào)告給調(diào)用方」程序錯(cuò)誤是「當(dāng)前這條執(zhí)行路徑斷了」。業(yè)務(wù)錯(cuò)誤返回errorerror是內(nèi)置接口只有一個(gè)方法Error() string。沒出錯(cuò)返回nil有錯(cuò)返回非nil。下面要求參數(shù)必須是9才打印九九表傳8時(shí)返回錯(cuò)誤。注意處理完錯(cuò)誤之后main里的「其余邏輯」仍然會(huì)執(zhí)行。packagemainimport(errorsfmt)// 業(yè)務(wù)校驗(yàn)失敗返回 error不中斷整個(gè)程序funcnn(aint)(bool,error){ifa!9{returnfalse,errors.New(請輸入9)}fori:1;ia;i{fork:1;ki;k{fmt.Printf(%d * %d %d ,k,i,i*k)}fmt.Println()}returntrue,nil}funcmain(){success,err:nn(8)iferr!nil{fmt.Println(err,執(zhí)行失敗)}else{fmt.Println(success,執(zhí)行成功)}// 業(yè)務(wù)錯(cuò)誤只是「這次調(diào)用失敗」后面的代碼照常走fmt.Println(其余邏輯)}輸出類似請輸入9 執(zhí)行失敗 其余邏輯要點(diǎn)用errors.New(說明)或fmt.Errorf(a%d 非法, a)創(chuàng)建錯(cuò)誤。調(diào)用方標(biāo)準(zhǔn)姿勢if err ! nil { ... }。返回錯(cuò)誤后函數(shù)正常結(jié)束調(diào)用方可以打日志、換參數(shù)重試也可以繼續(xù)干別的事。程序錯(cuò)誤panic把「請輸入 9」改成panic。一旦觸發(fā)從panic那一行開始同函數(shù)里后面的代碼不會(huì)再跑。先看沒有recover的情況——進(jìn)程會(huì)直接崩潰packagemainimportfmtfuncnn(aint)(bool,error){ifa!9{panic(請輸入9)// 程序錯(cuò)誤直接中斷當(dāng)前執(zhí)行路徑}fori:1;ia;i{fork:1;ki;k{fmt.Printf(%d * %d %d ,k,i,i*k)}fmt.Println()}returntrue,nil}funcmain(){success,err:nn(8)iferr!nil{fmt.Println(err,執(zhí)行失敗)}else{fmt.Println(success,執(zhí)行成功)}// 上面已經(jīng) panic這行永遠(yuǎn)走不到fmt.Println(其余邏輯)}運(yùn)行后會(huì)看到 panic 棧且沒有「其余邏輯」。這就是和業(yè)務(wù)錯(cuò)誤最大的差別。defer 用法defer會(huì)把一次函數(shù)調(diào)用推遲到當(dāng)前函數(shù)即將返回之前執(zhí)行常用于關(guān)文件、解鎖、以及配合recover捕獲panic?;卷樞蛳葘懻_壿媎efer的內(nèi)容最后跑。packagemainimportfmtfuncmain(){fmt.Println(開始)deferfmt.Println(defer收尾)fmt.Println(業(yè)務(wù)邏輯)}輸出開始 業(yè)務(wù)邏輯 defer收尾多個(gè)defer按后進(jìn)先出LIFO執(zhí)行——最后注冊的最先跑packagemainimportfmtfuncmain(){deferfmt.Println(第一)deferfmt.Println(第二)deferfmt.Println(第三)fmt.Println(函數(shù)體)}輸出函數(shù)體 第三 第二 第一和錯(cuò)誤處理相關(guān)的關(guān)鍵點(diǎn)recover必須寫在defer里才有效普通代碼里調(diào)用recover()拿不到 panic。defer常用來保證「出錯(cuò)也要收尾」關(guān)文件、釋放鎖、打印現(xiàn)場。參數(shù)在寫defer那一刻就算好不是真正執(zhí)行時(shí)才算。更多細(xì)節(jié)見「Go 函數(shù)」一文的 defer 小節(jié)。用deferrecover兜住 panic加上recover后進(jìn)程不會(huì)崩潰可以打印 panic 信息但panic發(fā)生點(diǎn)之后的代碼仍然不會(huì)執(zhí)行。packagemainimportfmtfuncnn(aint)(bool,error){ifa!9{panic(請輸入9)}fori:1;ia;i{fork:1;ki;k{fmt.Printf(%d * %d %d ,k,i,i*k)}fmt.Println()}returntrue,nil}funcmain(){deferfunc(){ifinfo:recover();info!nil{fmt.Println(捕獲到程序錯(cuò)誤:,info)}}()success,err:nn(8)// 這里 paniciferr!nil{fmt.Println(err,執(zhí)行失敗)}else{fmt.Println(success,執(zhí)行成功)}// panic 之后同函數(shù)里這些代碼依然不走fmt.Println(其余邏輯)}輸出類似捕獲到程序錯(cuò)誤: 請輸入9沒有「執(zhí)行失敗 / 執(zhí)行成功」也沒有「其余邏輯」。recover的作用是攔住崩潰不是從 panic 下一行接著跑。怎么選情況建議用戶輸錯(cuò)、查不到數(shù)據(jù)、權(quán)限不夠返回error庫函數(shù)約定用error如os.Open跟著返回 / 包裝error明顯的編程錯(cuò)誤、不可繼續(xù)的狀態(tài)可以panic服務(wù)入口想避免整個(gè)進(jìn)程掛掉邊界處deferrecover打日志后返回錯(cuò)誤響應(yīng)入門階段優(yōu)先練好if err ! nilpanic/recover留給真正的異常路徑不要拿來代替普通業(yè)務(wù)校驗(yàn)。作業(yè)返回 error寫divide(a, b int) (int, error)b 0時(shí)返回錯(cuò)誤否則返回商。在main里分別用(10, 2)和(10, 0)調(diào)用并處理。業(yè)務(wù)錯(cuò)誤對比改寫文中的nn允許傳入19任意整數(shù)打印對應(yīng)乘法表小于1或大于9時(shí)返回error。調(diào)用失敗后打印一句「其余邏輯」確認(rèn)它仍會(huì)執(zhí)行。defer 順序在main里注冊三個(gè)defer打印A、B、C再打印一句「函數(shù)體」觀察輸出順序是否為「函數(shù)體 → C → B → A」。panic 與 recover寫一個(gè)函數(shù)mustPositive(n int)n 0時(shí)panic。在main里用deferrecover捕獲打印 panic 信息并在調(diào)用后面再寫一句fmt.Println(其余邏輯)驗(yàn)證它不會(huì)執(zhí)行。綜合練習(xí)寫printTable(n int) error業(yè)務(wù)錯(cuò)誤版和mustPrintTable(n int)n ! 9就panic。在main里先演示返回error后后續(xù)代碼仍運(yùn)行再用recover演示panic被兜住但后續(xù)代碼不運(yùn)行。