8 Go錯(cuò)誤與異常:error、panic、recover

一、程序設(shè)計(jì)中的錯(cuò)誤和異常處理

對(duì)錯(cuò)誤和異常處理的支持是現(xiàn)代編程語言的標(biāo)配,在Go語言中,程序的錯(cuò)誤由內(nèi)建的error接口支持,errors標(biāo)準(zhǔn)包提供了最基本的錯(cuò)誤處理方法,用戶還可自定義錯(cuò)誤處理。而程序的異常處理通常由panic和recover實(shí)現(xiàn)觸發(fā)和終止。

如果你接觸過類似JAVA、python等語言,你一定了解try...catch...結(jié)構(gòu),但是Go中是沒有try...catch...的,相反你會(huì)看到程序中充斥著大量的if err != nil { ...}這樣的結(jié)構(gòu),這種錯(cuò)誤處理方式和Go的設(shè)計(jì)理念有關(guān),在此之前我們先明確什么是錯(cuò)誤?很么是異常?

  • 什么是錯(cuò)誤?

錯(cuò)誤是程序中可能出現(xiàn)的問題,比如連接數(shù)據(jù)庫失敗,連接網(wǎng)絡(luò)失敗等,在程序設(shè)計(jì)中,錯(cuò)誤處理是業(yè)務(wù)的一部分。

Go內(nèi)建一個(gè)error接口類型作為go的錯(cuò)誤標(biāo)準(zhǔn)處理
//go的錯(cuò)誤處理接口
type error interface {
    Error() string
}
//errors標(biāo)準(zhǔn)包定義了一個(gè)錯(cuò)誤處理的實(shí)現(xiàn)
// Package errors implements functions to manipulate errors.
package errors

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}
  • 什么是異常?

異常是指在不該出現(xiàn)問題的地方出現(xiàn)問題,是預(yù)料之外的,比如空指針引用,下標(biāo)越界,向空map添加鍵值等。

Go中內(nèi)建了panic和recover實(shí)現(xiàn)對(duì)異常的觸發(fā)和終止
人為制造一些會(huì)被自動(dòng)觸發(fā)的異常
    //1.空指針取值
    var aPtr *int //指針聲明本身為空,取不了值
    fmt.Println(*aPtr)

    //2.下標(biāo)越界
    var mm = []int{1, 2, 334, 345, 3}
    fmt.Println(mm[6])

    //3.向空map添加鍵值對(duì)
    var amap map[string]int //只聲明沒初始化的map沒有分配堆內(nèi)存
    amap["aaa"] = 111

手工觸發(fā)異常并終止異常
//手工觸發(fā)異常
func triggerPanic(){
    panic("這是一個(gè)手工觸發(fā)的恐慌!")
}
    
//調(diào)用處終止異常轉(zhuǎn)為錯(cuò)誤處理
func main(){
    //處理此函數(shù)中調(diào)用其他函數(shù)時(shí)產(chǎn)生的恐慌
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕捉一個(gè)恐慌,恐慌信息為:", err)
            
            ...
            
            fmt.Println("這里還能繼續(xù)運(yùn)行?。。?!")
        }

    }()
    
    //調(diào)用一個(gè)會(huì)觸發(fā)恐慌的函數(shù)
    triggerPanic()
    
}
    

理解了錯(cuò)誤和異常的真正含義,我們就能理解Go的錯(cuò)誤和異常處理的設(shè)計(jì)意圖。傳統(tǒng)的try...catch...結(jié)構(gòu),很容易讓開發(fā)人員把錯(cuò)誤和異?;鞛橐徽?,甚至把業(yè)務(wù)錯(cuò)誤處理的一部分當(dāng)做異常來處理,于是你會(huì)在程序中看到一大堆的catch...。

Go開發(fā)團(tuán)隊(duì)認(rèn)為錯(cuò)誤應(yīng)該明確地當(dāng)成業(yè)務(wù)的一部分,任何可以預(yù)見的問題都需要做錯(cuò)誤處理,于是在Go代碼中,任何調(diào)用者在接收函數(shù)返回值的同時(shí)也需要對(duì)錯(cuò)誤進(jìn)行處理,以防遺漏任何運(yùn)行時(shí)可能的錯(cuò)誤。

異常則是意料之外的,甚至你認(rèn)為在編碼中不可能發(fā)生的,Go遇到異常會(huì)自動(dòng)觸發(fā)panic(恐慌),看這意思就知道,程序的異常是非常嚴(yán)重的,觸發(fā)panic程序會(huì)自動(dòng)退出。除了程序自動(dòng)觸發(fā)異常,一些你認(rèn)為不可允許的情況你也可以手動(dòng)觸發(fā)異常,異常應(yīng)在開發(fā)中盡早的發(fā)現(xiàn)。

然而在Go中除了觸發(fā)異常,還可以終止異常并可選的對(duì)異常進(jìn)行錯(cuò)誤處理,也就是說,錯(cuò)誤和異常是可以相互轉(zhuǎn)換的。

  • 錯(cuò)誤轉(zhuǎn)異常:

當(dāng)你在接收到錯(cuò)誤信息,業(yè)務(wù)場(chǎng)景中認(rèn)為該錯(cuò)誤會(huì)影響下一流程時(shí),可選擇手動(dòng)觸發(fā)panic(),異常退出。

  • 異常轉(zhuǎn)錯(cuò)誤:

在程序的調(diào)用處,也可以在最根部的main()函數(shù)體內(nèi),在此調(diào)用recover()接收任何被觸發(fā)的異常,如何這些異常可以出來,可以選擇轉(zhuǎn)成錯(cuò)誤處理防止程序退出。

二、Go程序開發(fā)中的錯(cuò)誤處理規(guī)范

1.當(dāng)錯(cuò)誤原因只有一個(gè)時(shí),不返回錯(cuò)誤,直接返回一個(gè)布爾值。

//如一些判斷類的函數(shù)IsXXX(),建議返回布爾值
func IsValid(hostName string) bool {
    return hostName == "www.golang.org"
}

2.程序處理中不存在失敗情況時(shí),不返回任何錯(cuò)誤。

//如某些設(shè)置狀態(tài)變量,結(jié)構(gòu)體屬性
var status int
func SetStatus(i int){
    status = i
}

3.當(dāng)錯(cuò)誤原因存在多個(gè)且有多個(gè)返回值時(shí),error返回參數(shù)總在最后一個(gè)。

//官方示例
package net/http
func Get(url string) (resp *Response, err error) {
    return DefaultClient.Get(url)
}

//調(diào)用處
resp, err := http.Get(url)
if err != nil {
    return nill, err
}

4.一個(gè)工程化的項(xiàng)目,其錯(cuò)誤原因應(yīng)該統(tǒng)一定義。

//1.最簡(jiǎn)單的方式:使用標(biāo)準(zhǔn)error包預(yù)定義錯(cuò)誤類型
var ERR_NOT_FOUND = errors.New("[error]:File is Not Fount!")
//定義其他類型...


//2.實(shí)現(xiàn)error接口,自定義錯(cuò)誤類型

//自定義錯(cuò)誤結(jié)構(gòu)體
type MyError1 struct {
    Code    int
    Msg string
}

func (me *MyError1) Error() string {
    info := fmt.Sprintf("[MyError1]:Code:%d Mes:%s",me.Code,me.Msg)
    return info
}

//自定義錯(cuò)誤的工廠方法
func NewMyError1(code int,msg string) *MyError1 {
    err := new(MyError1)
    err.Code = code
    err.Msg = string
    return err
}

//定義其他錯(cuò)誤類型...

5.一個(gè)健壯的程序中,調(diào)用者盡可能不要忽略返回的錯(cuò)誤。

//除非你很有信息這錯(cuò)誤不會(huì)影響下面的流程,否則還是建議處理err的好,要謹(jǐn)記一切網(wǎng)絡(luò)都不可信!
resp, _ := http.Get(url)


//類似以上的要少做,一般函數(shù)設(shè)計(jì)中有返回error的,檢查做錯(cuò)誤處理都是必要的!

6.defer處理要在錯(cuò)誤處理之后

//謹(jǐn)記先處理err,否則退出程序
resp, err := http.Get(url)
if err != nil {
    return nill, err
}
defer resp.Body.Close()

7.錯(cuò)誤在調(diào)用棧中傳遞時(shí),每層調(diào)用棧都務(wù)必日志記錄,以備溯源。
在開發(fā)過程中,最快的debug方式就是打印日志,在錯(cuò)誤處理的時(shí)候輸出日志,一旦程序報(bào)錯(cuò),通過日志溯源一般都能很快定位,與其在debug時(shí)打斷點(diǎn),日志溯源效率會(huì)高很多

8.有些失敗是偶然性的,如網(wǎng)絡(luò)錯(cuò)誤,可以嘗試一定次數(shù)后再返回失敗。這種錯(cuò)誤處理在網(wǎng)絡(luò)IO的開發(fā)中非常常見。

9.如果業(yè)務(wù)上調(diào)用者不關(guān)心錯(cuò)誤,也可以不必返回錯(cuò)誤。與其說是不關(guān)心,倒不如說關(guān)心也處理不了,比如一些資源清理類的操作,如內(nèi)部出錯(cuò)一般都打印日志即可。

10.出錯(cuò)時(shí),不要輕易忽略除錯(cuò)誤外的其他返回值。一般開發(fā)中我們會(huì)習(xí)慣性的在出錯(cuò)時(shí)把除錯(cuò)誤外的其他返回值置為零值,如果某些計(jì)算結(jié)果外部調(diào)用者需要,則不可忽略。比如,當(dāng)讀取文件發(fā)生錯(cuò)誤時(shí),Read函數(shù)會(huì)返回可以讀取的字節(jié)數(shù)以及錯(cuò)誤信息。對(duì)于這種情況,應(yīng)該將讀取到的字符串和錯(cuò)誤信息一起打印出來。

三、Go程序開發(fā)中的異常處理規(guī)范

1.開發(fā)階段,堅(jiān)持速錯(cuò);

開發(fā)階段盡早發(fā)現(xiàn)錯(cuò)誤,這是常識(shí)。

2.部署階段,終止異常做必要的錯(cuò)誤處理

//某些業(yè)務(wù)場(chǎng)景中會(huì)觸發(fā)panic,但在線上環(huán)境千萬不要暴露丑陋的panic,應(yīng)盡量展示用戶友好的信息提示

func funcA() (err error) {
    //A在調(diào)用B時(shí)使用defer延遲接收panic,并把它轉(zhuǎn)為錯(cuò)誤處理
    defer func() {
        if p := recover(); p != nil {
            fmt.Println("panic recover! p:", p)
            str, ok := p.(string)
            if ok {
                err = errors.New(str)
            } else {
                err = errors.New("panic")
            }
            //打印輸出一些用戶友好的信息
            debug.PrintStack()
        }
    }()
    return funcB()
}

func funcB() error {
    // B的業(yè)務(wù)處理流程中有一些不可處理的分支,手工觸發(fā)panic
    panic("foo")
}

3.不應(yīng)該出現(xiàn)的情況,手動(dòng)觸發(fā)異常

switch s := suit(drawCard()); s {
    case "Spades":
    // ...
    case "Hearts":
    // ...
    case "Diamonds":
    // ... 
    case "Clubs":
    // ...
    default:
        panic(fmt.Sprintf("invalid suit %v", s))
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容