Go入門:理解Go的Error處理

在Go中,對于處理錯誤一般分為兩種情況: 錯誤和異常.

在Go中,錯誤的處理一般都是通過 error接口來指定;異常通常都是通過panic來指定。


go的Error

go Error就是一個普通的接口罩抗,普通的值。(https://pkg.go.dev/builtin#error)

type error interface {
    Error() string
}



我們經(jīng)常使用 errors.New()來返回一個error對象

分析error包核心代碼

package errors

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
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
}

如下面的例子:

package main

func main(){
    NotFoundErr := errors.New("沒有找到文件目錄")
}

在go的基礎(chǔ)庫中灿椅,也引用了大量自定義的Error套蒂,例如:
https://go.dev/src/bufio/bufio.go

var (
    ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
    ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
    ErrBufferFull        = errors.New("bufio: buffer full")
    ErrNegativeCount     = errors.New("bufio: negative count")
)


有一個問題钞支,值得思考;為什么errors.New()返回的是內(nèi)部errorString對象的指針呢?


// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
    return &errorString{text}
}

因為go比較兩個結(jié)構(gòu)體是否相等時操刀,是依次比較每個字段烁挟,如果不返回結(jié)構(gòu)體指針的話,當字段s相同時就會被認為時同一個錯誤


Error Vs Exception

Go的處理異常邏輯是不引入Exception骨坑,支持多參數(shù)返回撼嗓。
所以我們可以很輕易的在函數(shù)簽名中攜帶實現(xiàn)了error interface的對象,交給調(diào)用者來判定卡啰。
如果一個函數(shù)返回了 value, error静稻,你不能對這個 value 做任何假設(shè),必須先判定 error匈辱。唯一可以忽略 error 的是,如果你連 value 也不關(guān)心杀迹。

其中,Go也有panic機制亡脸,Go panic 意味著 fatal error(就是掛了)。不能假設(shè)調(diào)用者來解決 panic树酪,意味著代碼不能繼續(xù)運行浅碾。

在Go中,使用多個返回值和一個簡單的約定续语,
Go 解決了讓程序員知道什么時候出了問題垂谢,并為真正的異常情況保留了 panic。


對于Panic和error什么時候用比較合適疮茄?

  1. 在服務(wù)初始化失敗時滥朱,一些main函數(shù)里面的強依賴的基礎(chǔ)組建,例如:mysql/redis/kafka/mq等力试,當連接錯誤時徙邻,必須panic。
  2. 在代碼中讀取配置文件出錯時畸裳,例如:讀取配置中心出錯缰犁,讀取Apollo出錯,這些基礎(chǔ)配置的讀取怖糊,如果出錯帅容,一定要panic,因為是強依賴伍伤,會影響后續(xù)的程序正常運行并徘。
  3. 在go語言中,也有異常捕獲(recover)機制, 在對于弱依賴出錯時嚷缭,可以recover.
    4 例如不可恢復(fù)的錯誤饮亏,例如:索引越界耍贾,不可恢復(fù)的環(huán)境問題,內(nèi)存溢出路幸,都推薦使用panic來讓程序直接退出荐开。


go的sentinel Error

這個單詞也可以叫:哨兵error.
先說下結(jié)論: 不建議使用sentinel Error


sentinel是什么?
對于預(yù)定義的特定錯誤简肴,我們叫它為 sentinel error (哨兵Error)晃听,這個名字來源于計算機編程中使用一個特定值來表示不可能進行進一步處理的做法。
對于Sentienl Error的例子可以參考 io.EOF,如下圖:

image.png

使用 sentinel 值是最不靈活的錯誤處理策略砰识,因為調(diào)用方必須使用 == 將結(jié)果與預(yù)先聲明的值進行比較能扒。當您想要提供更多的上下文時,這就出現(xiàn)了一個問題辫狼,因為返回一個不同的錯誤將破壞相等性檢查初斑。



為什么不推薦使用 sentinel?

  1. Sentinel errors 會成為你 API 公共部分。
  2. Sentinel errors 在兩個包之間創(chuàng)建了依賴膨处。


Error Types

Error type 是實現(xiàn)了 error 接口的自定義類型见秤。例如 MyError 類型記錄了文件和行號以展示發(fā)生了什么。

package main

import "fmt"

type  MyError struct {
    Msg string
    File string
    Line int
}


func (e *MyError) Error() string {
    return fmt.Sprintf("%s:%d:%s",e.File,e.Line,e.Msg)
}

func test() error {
    return &MyError{"something happend","server.go",42}
}

func main()  {
    err := test()
    if err != nil {
        fmt.Println(err)
    }
}```


```go

因為 MyError 是一個 type真椿,調(diào)用者可以使用斷言轉(zhuǎn)換成這個類型鹃答,來獲取更多的上下文信息。

package main

import "fmt"

type  MyError struct {
    Msg string
    File string
    Line int
}


func (e *MyError) Error() string {
    return fmt.Sprintf("%s:%d:%s",e.File,e.Line,e.Msg)
}

func test() error {
    return &MyError{"something happend","server.go",42}
}

func main()  {
    err := test()
    switch err := err.(type) {
    case nil:
        fmt.Println("沒有error")
    case *MyError:
        fmt.Println("error occureed on line:",err.Line)

    }
}




與錯誤值相比突硝,錯誤類型的一大改進是它們能夠包裝底層錯誤以提供更多上下文测摔。
一個不錯的例子就是 os.PathError 他提供了底層執(zhí)行了什么操作、那個路徑出了什么問題解恰。

image.png

結(jié)論: 調(diào)用者要使用類型斷言和類型 switch锋八,就要讓自定義的 error 變?yōu)?public。這種模型會導(dǎo)致和調(diào)用者產(chǎn)生強耦合修噪,從而導(dǎo)致 API 變得脆弱查库。
結(jié)論是盡量避免使用 error types,雖然錯誤類型比 sentinel errors 更好黄琼,因為它們可以捕獲關(guān)于出錯的更多上下文樊销,但是 error types 共享 error values 許多相同的問題。
因此脏款,我的建議是避免錯誤類型围苫,或者至少避免將它們作為公共 API 的一部分。


Handling Error

  1. Indented Flow is for errors
    無錯誤的正常流程代碼撤师,將成為一條直線剂府,而不是縮進的代碼。

例如:

f, err := os.Open(path)
if err != nil {
  // handle error
}
// do stuff

f,err := os.Open(path)
if err == nil {
  // do stuff
}
// handle error

2 Eliminate error handling by eliminating errors
通過消除錯誤來消除錯誤處理

func AuthenticateRequest(r *Request) error {
  err := authenticate(r.User)
  if err != nil {
      return err 
}
    return nil
}


// 修改后
func  AuthenticateRequest(r *Request) error {
    return  authenticate(r.User)
}


Wrap errors

通過使用 pkg/errors包剃盾,您可以向錯誤值添加上下文腺占,這種方式即可以由人淤袜,也可以由機器檢查

func Write(w io.Write, buf []byte)error {

  _,err := w.Write(buf)
  return errors.Wrap(err,"write failed")
}


  • 在你的應(yīng)用代碼中,可以使用 errors.New 或者 errors.Errorf 返回錯誤
  • 如果和其他庫進行協(xié)作衰伯,考慮使用 errors.Wrap 或者 errors.Wrapf保存堆棧信息铡羡。同樣適用于和標準庫協(xié)作的時候
  • 直接返回錯誤,而不是每個地方都到處打日志
    在程序的頂部或者工作的goruoutine(請求入口)意鲸,使用 %+v 把堆棧詳情打出來烦周!

go1.13之后的error



go1.13為 errors 和 fmt 標準庫包引入了新特性,以簡化處理包含其他錯誤的錯誤怎顾。其中最重要的是: 包含另一個錯誤的 error 可以實現(xiàn)返回底層錯誤的 Unwrap 方法读慎。如果 e1.Unwrap() 返回 e2,那么我們說 e1 包裝 e2槐雾,您可以展開 e1 以獲得 e2夭委。
按照此約定,我們可以為上面的 QueryError 類型指定一個 Unwrap 方法蚜退,該方法返回其包含的錯誤:

image.png

go1.13 errors 包包含兩個用于檢查錯誤的新函數(shù):Is 和 As闰靴。

image.png
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钻注,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌配猫,老刑警劉巖幅恋,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異泵肄,居然都是意外死亡捆交,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門腐巢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來品追,“玉大人,你說我怎么就攤上這事冯丙∪馔撸” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵胃惜,是天一觀的道長泞莉。 經(jīng)常有香客問我,道長船殉,這世上最難降的妖魔是什么鲫趁? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮利虫,結(jié)果婚禮上挨厚,老公的妹妹穿的比我還像新娘堡僻。我一直安慰自己,他們只是感情好疫剃,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布钉疫。 她就那樣靜靜地躺著,像睡著了一般慌申。 火紅的嫁衣襯著肌膚如雪陌选。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天蹄溉,我揣著相機與錄音咨油,去河邊找鬼。 笑死柒爵,一個胖子當著我的面吹牛役电,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棉胀,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼法瑟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唁奢?” 一聲冷哼從身側(cè)響起霎挟,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎麻掸,沒想到半個月后酥夭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡脊奋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年熬北,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诚隙。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡讶隐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出久又,到底是詐尸還是另有隱情巫延,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布籽孙,位于F島的核電站烈评,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏犯建。R本人自食惡果不足惜讲冠,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望适瓦。 院中可真熱鬧竿开,春花似錦谱仪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至列荔,卻和暖如春敬尺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贴浙。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工砂吞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人崎溃。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓蜻直,卻偏偏與公主長得像,于是被迫代替她去往敵國和親袁串。 傳聞我的和親對象是個殘疾皇子概而,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容