1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        萬字長文:Go error 處理最佳實踐

        共 10042字,需瀏覽 21分鐘

         ·

        2021-11-30 23:45

        今天分享 go 語言 error 處理的最佳實踐,了解當前 error 的缺點、妥協(xié)以及使用時注意事項。文章內(nèi)容較長,干貨也多,建義收藏

        什么是 error

        大家都知道 error[1] 是源代碼內(nèi)嵌的接口類型。根據(jù)導出原則,只有大寫的才能被其它源碼包引用,但是 error 屬于 predeclared identifiers 預定義的,并不是關鍵字,細節(jié)參考int make 居然不是關鍵字?

        //?The?error?built-in?interface?type?is?the?conventional?interface?for
        //?representing?an?error?condition,?with?the?nil?value?representing?no?error.
        type?error?interface?{
        ?Error()?string
        }

        error 只有一個方法 Error() string 返回錯誤消息

        //?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
        }

        一般我們創(chuàng)建 error 時只需要調(diào)用 errors.New("error from somewhere") 即可,底層就是一個字符串結構體 errorStrings

        當前 error 有哪些問題

        func?Test()?error?{
        ?if?err?:=?func1();?err?!=?nil?{
        ??return?err
        ?}
        ??......
        }

        這是常見的用法,也最被人詬病,很多人覺得不如 try-catch 用法簡潔,有人戲稱 go 源碼錯誤處理占一半

        import?sys

        try:
        ????f?=?open('myfile.txt')
        ????s?=?f.readline()
        ????i?=?int(s.strip())
        except?OSError?as?err:
        ????print("OS?error:?{0}".format(err))
        except?ValueError:
        ????print("Could?not?convert?data?to?an?integer.")
        except?BaseException?as?err:
        ????print(f"Unexpected?{err=},?{type(err)=}")
        ????raise

        比如上面是 python try-catch 的用法,先寫一堆邏輯,不處理異常,最后統(tǒng)一捕獲

        let?mut?cfg?=?self.check_and_copy()?;

        相比來說 rust Result 模式更簡潔,一個 ? 就代替了我們的操作。但是 error 的繁瑣判斷是當前的痛點嘛?顯然不是,尤其喜歡 c 語言的人,反而喜歡每次都做判斷

        在我看來 go 的痛點不是缺少泛型,不是 error 太挫,而是 GC 太弱,尤其對大內(nèi)存非常不友好,這方面可以參考真實環(huán)境下大內(nèi)存 Go 服務性能優(yōu)化一例

        當前 error 的問題有兩點:

        1. 無法 wrap 更多的信息,比如調(diào)用棧,比如層層封裝的 error 消息
        2. 無法很好的處理類型信息,比如我想知道錯誤是 io 類型的,還是 net 類型的

        1.Wrap 更多的消息

        這方面有很多輪子,最著名的就是 https://github.com/pkg/errors, 我司也重度使用,主要功能有三個:

        1. Wrap 封裝底層 error, 增加更多消息,提供調(diào)用棧信息,這是原生 error 缺少的
        2. WithMessage 封裝底層 error, 增加更多消息,但不提供調(diào)用棧信息
        3. Cause 返回最底層的 error, 剝?nèi)訉拥?wrap
        import?(
        ???"database/sql"
        ???"fmt"

        ???"github.com/pkg/errors"
        )

        func?foo()?error?{
        ???return?errors.Wrap(sql.ErrNoRows,?"foo?failed")
        }

        func?bar()?error?{
        ???return?errors.WithMessage(foo(),?"bar?failed")
        }

        func?main()?{
        ???err?:=?bar()
        ???if?errors.Cause(err)?==?sql.ErrNoRows?{
        ??????fmt.Printf("data?not?found,?%v\n",?err)
        ??????fmt.Printf("%+v\n",?err)
        ??????return
        ???}
        ???if?err?!=?nil?{
        ??????//?unknown?error
        ???}
        }
        /*Output:
        data?not?found,?bar?failed:?foo?failed:?sql:?no?rows?in?result?set
        sql:?no?rows?in?result?set
        foo?failed
        main.foo
        ????/usr/three/main.go:11
        main.bar
        ????/usr/three/main.go:15
        main.main
        ????/usr/three/main.go:19
        runtime.main
        ????...
        */

        這是測試代碼,當用 %v 打印時只有原始錯誤信息,%+v 時打印完整調(diào)用棧。當 go1.13 后,標準庫 errors 增加了 Wrap 方法

        func?ExampleUnwrap()?{
        ?err1?:=?errors.New("error1")
        ?err2?:=?fmt.Errorf("error2:?[%w]",?err1)
        ?fmt.Println(err2)
        ?fmt.Println(errors.Unwrap(err2))
        ?//?Output
        ?//?error2:?[error1]
        ?//?error1
        }

        標準庫沒有提供增加調(diào)用棧的方法,fmt.Errorf 指定 %w 時可以 wrap error, 但整體來講,并沒有 https://github.com/pkg/errors 庫好用

        2.錯誤類型

        這個例子來自 ITNEXT[2]

        import?(
        ???"database/sql"
        ???"fmt"
        )

        func?foo()?error?{
        ???return?sql.ErrNoRows
        }

        func?bar()?error?{
        ???return?foo()
        }

        func?main()?{
        ???err?:=?bar()
        ???if?err?==?sql.ErrNoRows?{
        ??????fmt.Printf("data?not?found,?%+v\n",?err)
        ??????return
        ???}
        ???if?err?!=?nil?{
        ??????//?Unknown?error
        ???}
        }
        //Outputs:
        //?data?not?found,?sql:?no?rows?in?result?set

        有時我們要處理類型信息,比如上面例子,判斷 err 如果是 sql.ErrNoRows 那么視為正常,data not found 而己,類似于 redigo 里面的 redigo.Nil 表示記錄不存在

        func?foo()?error?{
        ???return?fmt.Errorf("foo?err,?%v",?sql.ErrNoRows)
        }

        但是如果 foo 把 error 做了一層 wrap 呢?這個時候錯誤還是 sql.ErrNoRows 嘛?肯定不是,這點沒有 python try-catch 錯誤處理強大,可以根據(jù)不同錯誤 class 做出判斷。那么 go 如何解決呢?答案是 go1.13 新增的 Is[3] 和 As

        import?(
        ???"database/sql"
        ???"errors"
        ???"fmt"
        )

        func?bar()?error?{
        ???if?err?:=?foo();?err?!=?nil?{
        ??????return?fmt.Errorf("bar?failed:?%w",?foo())
        ???}
        ???return?nil
        }

        func?foo()?error?{
        ???return?fmt.Errorf("foo?failed:?%w",?sql.ErrNoRows)
        }

        func?main()?{
        ???err?:=?bar()
        ???if?errors.Is(err,?sql.ErrNoRows)?{
        ??????fmt.Printf("data?not?found,??%+v\n",?err)
        ??????return
        ???}
        ???if?err?!=?nil?{
        ??????//?unknown?error
        ???}
        }
        /*?Outputs:
        data?not?found,??bar?failed:?foo?failed:?sql:?no?rows?in?result?set
        */

        還是這個例子,errors.Is 會遞歸的 Unwrap err, 判斷錯誤是不是 sql.ErrNoRows,這里個小問題,Is 是做的指針地址判斷,如果錯誤 Error() 內(nèi)容一樣,但是根 error 是不同實例,那么 Is 判斷也是 false, 這點就很扯

        func?ExampleAs()?{
        ?if?_,?err?:=?os.Open("non-existing");?err?!=?nil?{
        ??var?pathError?*fs.PathError
        ??if?errors.As(err,?&pathError)?{
        ???fmt.Println("Failed?at?path:",?pathError.Path)
        ??}?else?{
        ???fmt.Println(err)
        ??}
        ?}

        ?//?Output:
        ?//?Failed?at?path:?non-existing
        }

        errors.As[4] 判斷這個 err 是否是 fs.PathError 類型,遞歸調(diào)用層層查找,源碼后面再講解

        另外一個判斷類型或是錯誤原因的就是 https://github.com/pkg/errors 庫提供的 errors.Cause

        switch?err?:=?errors.Cause(err).(type)?{
        case?*MyError:
        ????????//?handle?specifically
        default:
        ????????//?unknown?error
        }

        在沒有 Is As 類型判斷時,需要很惡心的去判斷錯誤自符串

        func?(conn?*cendolConnectionV5)?serve()?{
        ?//?Buffer?needs?to?be?preserved?across?messages?because?of?packet?coalescing.
        ?reader?:=?bufio.NewReader(conn.Connection)
        ?for?{
        ??msg,?err?:=?conn.readMessage(reader)
        ??if?err?!=?nil?{
        ???if?netErr,?ok?:=?strings.Contain(err.Error(),?"temprary");?ok???{
        ?????continue
        ???}
        ??}

        ??conn.processMessage(msg)
        ?}
        }

        想必接觸 go 比較早的人一定很熟悉,如果 conn 從網(wǎng)絡接受到的連接錯誤是 temporary 臨時的那么可以 continue 重試,當然最好 backoff sleep 一下

        當然現(xiàn)在新增加了 net.Error 類型,實現(xiàn)了 Temporary 接口,不過也要廢棄了,請參考#45729[5]

        源碼實現(xiàn)

        1.github.com/pkg/errors 庫如何生成 warapper error

        //?Wrap?returns?an?error?annotating?err?with?a?stack?trace
        //?at?the?point?Wrap?is?called,?and?the?supplied?message.
        //?If?err?is?nil,?Wrap?returns?nil.
        func?Wrap(err?error,?message?string)?error?{
        ?if?err?==?nil?{
        ??return?nil
        ?}
        ?err?=?&withMessage{
        ??cause:?err,
        ??msg:???message,
        ?}
        ?return?&withStack{
        ??err,
        ??callers(),
        ?}
        }

        主要的函數(shù)就是 Wrap, 代碼實現(xiàn)比較簡單,查看如何追蹤調(diào)用棧可以查看源碼

        2.github.com/pkg/errorsCause 實現(xiàn)

        type?withStack?struct?{
        ?error
        ?*stack
        }

        func?(w?*withStack)?Cause()?error?{?return?w.error?}

        func?Cause(err?error)?error?{
        ?type?causer?interface?{
        ??Cause()?error
        ?}

        ?for?err?!=?nil?{
        ??cause,?ok?:=?err.(causer)
        ??if?!ok?{
        ???break
        ??}
        ??err?=?cause.Cause()
        ?}
        ?return?err
        }

        Cause 遞歸調(diào)用,如果沒有實現(xiàn) causer 接口,那么就返回這個 err

        3.官方庫如何生成一個 wrapper error

        官方?jīng)]有這樣的函數(shù),而是 fmt.Errorf 格式化時使用 %w

        e?:=?errors.New("this?is?a?error")
        w?:=?fmt.Errorf("more?info?about?it?%w",?e)
        func?Errorf(format?string,?a?...interface{})?error?{
        ?p?:=?newPrinter()
        ?p.wrapErrs?=?true
        ?p.doPrintf(format,?a)
        ?s?:=?string(p.buf)
        ?var?err?error
        ?if?p.wrappedErr?==?nil?{
        ??err?=?errors.New(s)
        ?}?else?{
        ??err?=?&wrapError{s,?p.wrappedErr}
        ?}
        ?p.free()
        ?return?err
        }

        func?(p?*pp)?handleMethods(verb?rune)?(handled?bool)?{
        ?if?p.erroring?{
        ??return
        ?}
        ?if?verb?==?'w'?{
        ??//?It?is?invalid?to?use?%w?other?than?with?Errorf,?more?than?once,
        ??//?or?with?a?non-error?arg.
        ??err,?ok?:=?p.arg.(error)
        ??if?!ok?||?!p.wrapErrs?||?p.wrappedErr?!=?nil?{
        ???p.wrappedErr?=?nil
        ???p.wrapErrs?=?false
        ???p.badVerb(verb)
        ???return?true
        ??}
        ??p.wrappedErr?=?err
        ??//?If?the?arg?is?a?Formatter,?pass?'v'?as?the?verb?to?it.
        ??verb?=?'v'
        ?}
        ??......
        }

        代碼也不難,handleMethods 時特殊處理 w, 使用 wrapError 封裝一下即可

        4.官方庫 Unwrap 實現(xiàn)

        func?Unwrap(err?error)?error?{
        ?u,?ok?:=?err.(interface?{
        ??Unwrap()?error
        ?})

        ?if?!ok?{
        ??return?nil
        ?}
        ?return?u.Unwrap()
        }

        也是遞歸調(diào)用,否則接口斷言失敗,返回 nil

        type?wrapError?struct?{
        ?msg?string
        ?err?error
        }

        func?(e?*wrapError)?Error()?string?{
        ?return?e.msg
        }

        func?(e?*wrapError)?Unwrap()?error?{
        ?return?e.err
        }

        上文 fmt.Errof 時生成的 error 結構體如上所示,Unwrap 直接返回底層 err

        5.官方庫 Is As 實現(xiàn)

        本段源碼分析來自 flysnow[6]

        func?Is(err,?target?error)?bool?{
        ?if?target?==?nil?{
        ??return?err?==?target
        ?}

        ?isComparable?:=?reflectlite.TypeOf(target).Comparable()
        ?
        ?//for循環(huán),把err一層層剝開,一個個比較,找到就返回true
        ?for?{
        ??if?isComparable?&&?err?==?target?{
        ???return?true
        ??}
        ??//這里意味著你可以自定義error的Is方法,實現(xiàn)自己的比較代碼
        ??if?x,?ok?:=?err.(interface{?Is(error)?bool?});?ok?&&?x.Is(target)?{
        ???return?true
        ??}
        ??//剝開一層,返回被嵌套的err
        ??if?err?=?Unwrap(err);?err?==?nil?{
        ???return?false
        ??}
        ?}
        }

        Is 函數(shù)比較簡單,遞歸層層檢查,如果是嵌套 err, 那就調(diào)用 Unwrap 層層剝開找到最底層 err, 最后判斷指針是否相等

        var?errorType?=?reflectlite.TypeOf((*error)(nil)).Elem()

        func?As(err?error,?target?interface{})?bool?{
        ????//一些判斷,保證target,這里是不能為nil
        ?if?target?==?nil?{
        ??panic("errors:?target?cannot?be?nil")
        ?}
        ?val?:=?reflectlite.ValueOf(target)
        ?typ?:=?val.Type()
        ?
        ?//這里確保target必須是一個非nil指針
        ?if?typ.Kind()?!=?reflectlite.Ptr?||?val.IsNil()?{
        ??panic("errors:?target?must?be?a?non-nil?pointer")
        ?}
        ?
        ?//這里確保target是一個接口或者實現(xiàn)了error接口
        ?if?e?:=?typ.Elem();?e.Kind()?!=?reflectlite.Interface?&&?!e.Implements(errorType)?{
        ??panic("errors:?*target?must?be?interface?or?implement?error")
        ?}
        ?targetType?:=?typ.Elem()
        ?for?err?!=?nil?{
        ?????//關鍵部分,反射判斷是否可被賦予,如果可以就賦值并且返回true
        ?????//本質(zhì)上,就是類型斷言,這是反射的寫法
        ??if?reflectlite.TypeOf(err).AssignableTo(targetType)?{
        ???val.Elem().Set(reflectlite.ValueOf(err))
        ???return?true
        ??}
        ??//這里意味著你可以自定義error的As方法,實現(xiàn)自己的類型斷言代碼
        ??if?x,?ok?:=?err.(interface{?As(interface{})?bool?});?ok?&&?x.As(target)?{
        ???return?true
        ??}
        ??//這里是遍歷error鏈的關鍵,不停的Unwrap,一層層的獲取err
        ??err?=?Unwrap(err)
        ?}
        ?return?false
        }

        代碼同樣是遞歸調(diào)用 As, 同時 Unwrap 最底層的 error, 然后用反射判斷是否可以賦值,如果可以,那么說明是同一類型

        ErrGroup 使用

        提到 error 就必須要提一下 golang.org/x/sync/errgroup, 適用如下場景:并發(fā)場景下,如果一個 goroutine 有錯誤,那么就要提前返回,并取消其它并行的請求

        func?ExampleGroup_justErrors()?{
        ?g?:=?new(errgroup.Group)
        ?var?urls?=?[]string{
        ??"http://www.golang.org/",
        ??"http://www.google.com/",
        ??"http://www.somestupidname.com/",
        ?}
        ?for?_,?url?:=?range?urls?{
        ??//?Launch?a?goroutine?to?fetch?the?URL.
        ??url?:=?url?//?https://golang.org/doc/faq#closures_and_goroutines
        ??g.Go(func()?error?{
        ???//?Fetch?the?URL.
        ???resp,?err?:=?http.Get(url)
        ???if?err?==?nil?{
        ????resp.Body.Close()
        ???}
        ???return?err
        ??})
        ?}
        ?//?Wait?for?all?HTTP?fetches?to?complete.
        ?if?err?:=?g.Wait();?err?==?nil?{
        ??fmt.Println("Successfully?fetched?all?URLs.")
        ?}
        }

        上面是官方給的例子,底層使用 context 來 cancel 其它請求,同步使用 WaitGroup, 原理非常簡單,代碼量非常少,感興趣的可以看源碼

        這里一定要注意三點:

        1. context 是誰傳進來的?其它代碼會不會用到,cancel 只能執(zhí)行一次,瞎比用會出問題
        2. g.Go 不帶 recover 的,為了程序的健壯,一定要自行 recover
        3. 并行的 goroutine 有一個錯誤就返回,而不是普通的 fan-out 請求后收集結果

        線上實踐注意的幾個問題

        1.error 與 panic

        查看 go 源代碼會發(fā)現(xiàn),源碼很多地方寫 panic, 但是工程實踐,尤其業(yè)務代碼不要主動寫 panic

        理論上 panic 只存在于 server 啟動階段,比如 config 文件解析失敗,端口監(jiān)聽失敗等等,所有業(yè)務邏輯禁止主動 panic

        根據(jù) CAP 理論,當前 web 互聯(lián)網(wǎng)最重要的是 AP, 高可用性才最關鍵(非銀行金融場景),程序啟動時如果有部分詞表,元數(shù)據(jù)加載失敗,都不能 panic, 提供服務才最關鍵,當然要有報警,讓開發(fā)第一時間感知當前服務了的 QOS 己經(jīng)降低

        最后說一下,所有異步的 goroutine 都要用 recover 去兜底處理

        2.錯誤處理與資源釋放

        func?worker(done?chan?error)?{
        ????err?:=?doSomething()
        ????result?:=?&result{}
        ????if?err?!=?nil?{
        ????????result.Err?=?err
        ????}
        ????done?<-?result
        }

        一般異步組裝數(shù)據(jù),都要分別啟動 goroutine, 然后把結果通過 channel 返回,result 結構體擁有 err 字段表示錯誤

        這里要注意,main 函數(shù)中 done channel 千萬不能 close, 因為你不知道 doSomething 會超時多久返回,寫 closed channel 直接 panic

        所以這里有一個準則:數(shù)據(jù)傳輸和退出控制,需要用單獨的 channel 不能混, 我們一般用 context 取消異步 goroutine, 而不是直接 close channels

        3.error 級聯(lián)使用問題

        package?main

        import?"fmt"

        type?myError?struct?{
        ?string
        }

        func?(i?*myError)?Error()?string?{
        ?return?i.string
        }

        func?Call1()?error?{
        ?return?nil
        }

        func?Call2()?*myError?{
        ?return?nil
        }

        func?main()?{
        ?err?:=?Call1()
        ?if?err?!=?nil?{
        ??fmt.Printf("call1?is?not?nil:?%v\n",?err)
        ?}

        ?err?=?Call2()
        ?if?err?!=?nil?{
        ??fmt.Printf("call2?err?is?not?nil:?%v\n",?err)
        ?}
        }

        這個問題非常經(jīng)典,如果復用 err 變量的情況下, Call2 返回的 error 是自定義類型,此時 err 類型是不一樣的,導致經(jīng)典的 error is not nil, but value is nil

        非常經(jīng)典的 Nil is not nil[7] 問題。解決方法就是 Call2 err 重新定義一個變量,當然最簡單就是統(tǒng)一 error 類型。有點難,尤其是大型項目

        4.并發(fā)問題

        go 內(nèi)置類型除了 channel 大部分都是非線程安全的,error 也不例外,先看一個例子

        package?main
        import?(
        ???"fmt"
        ???"github.com/myteksi/hystrix-go/hystrix"
        ???"time"
        )
        var?FIRST?error?=?hystrix.CircuitError{Message:"timeout"}
        var?SECOND?error?=?nil
        func?main()?{
        ???var?err?error
        ???go?func()?{
        ??????i?:=?1
        ??????for?{
        ?????????i?=?1?-?i
        ?????????if?i?==?0?{
        ????????????err?=?FIRST
        ?????????}?else?{
        ????????????err?=?SECOND
        ?????????}
        ?????????time.Sleep(10)
        ??????}
        ???}()
        ???for?{
        ??????if?err?!=?nil?{
        ?????????fmt.Println(err.Error())
        ??????}
        ??????time.Sleep(10)
        ???}
        }

        運行之前,大家先猜下會發(fā)生什么???

        zerun.dong$?go?run?panic.go
        hystrix:?timeout
        panic:?value?method?github.com/myteksi/hystrix-go/hystrix.CircuitError.Error?called?using?nil?*CircuitError?pointer

        goroutine?1?[running]:
        github.com/myteksi/hystrix-go/hystrix.(*CircuitError).Error(0x0,?0xc0000f4008,?0xc000088f40)
        ?:1?+0x86
        main.main()
        ?/Users/zerun.dong/code/gotest/panic.go:25?+0x82
        exit?status?2

        上面是測試的例子,只要跑一會,就一定發(fā)生 panic, 本質(zhì)就是 error 接口類型不是并發(fā)安全的

        //?沒有方法的interface
        type?eface?struct?{
        ????_type?*_type
        ????data??unsafe.Pointer
        }
        //?有方法的interface
        type?iface?struct?{
        ????tab??*itab
        ????data?unsafe.Pointer
        }

        所以不要并發(fā)對 error 賦值

        5.error 要不要忽略

        func?Test(){
        ?_?=?json.Marshal(xxxx)
        ?......
        }

        有的同學會有疑問,error 是否一定要處理?其實上面的 Marshal 都有可能失敗的

        如果換成其它函數(shù),當前實現(xiàn)可以忽略,不能保證以后還是兼容的邏輯,一定要處理 error,至少要打日志

        6.errWriter

        本例來自官方 blog[8], 有時我們想做 pipeline 處理,需要把 err 當成結構體變量

        _,?err?=?fd.Write(p0[a:b])
        if?err?!=?nil?{
        ????return?err
        }
        _,?err?=?fd.Write(p1[c:d])
        if?err?!=?nil?{
        ????return?err
        }
        _,?err?=?fd.Write(p2[e:f])
        if?err?!=?nil?{
        ????return?err
        }
        //?and?so?on

        上面是原始例子,需要一直做 if err != nil 的判斷,官方優(yōu)化的寫法如下

        type?errWriter?struct?{
        ????w???io.Writer
        ????err?error
        }

        func?(ew?*errWriter)?write(buf?[]byte)?{
        ????if?ew.err?!=?nil?{
        ????????return
        ????}
        ????_,?ew.err?=?ew.w.Write(buf)
        }

        //?使用時
        ew?:=?&errWriter{w:?fd}
        ew.write(p0[a:b])
        ew.write(p1[c:d])
        ew.write(p2[e:f])
        //?and?so?on
        if?ew.err?!=?nil?{
        ????return?ew.err
        }

        清晰簡潔,大家平時寫代碼可以多考濾一下

        7.何時打印調(diào)用棧

        官方庫無法 wrap 調(diào)用棧,所以 fmt.Errorf %w 不如 pkg/errors 庫實用,但是errors.Wrap 最好保證只調(diào)用一次,否則全是重復的調(diào)用棧

        我們項目的使用情況是 log error 級別的打印棧,warn 和 info 都不打印,當然 case by case 還得看實際使用情況

        8.Wrap前做判斷

        errors.Wrap(err,?"failed")

        通過查看源碼,如果 err 為 nil 的時候,也會返回 nil. 所以 Wrap 前最好做下判斷,建議來自 xiaorui.cc

        小結

        上面提到的線上實踐注意的幾個問題,都是實際發(fā)生的坑,慘痛的教訓,大家一定要多體會下。錯誤處理涵蓋內(nèi)容非常廣,本文不涉及分布式系統(tǒng)的錯誤處理、gRPC 錯誤傳播以及錯誤管理

        寫文章不容易,如果對大家有所幫助和啟發(fā),請大家?guī)兔c擊在看點贊,分享 三連

        關于 error 大家有什么看法,歡迎留言一起討論,大牛多留言 ^_^

        參考資料

        [1]

        builting.go error interface: https://github.com/golang/go/blob/master/src/builtin/builtin.go#L260,

        [2]

        ITNEXT: https://itnext.io/golang-error-handling-best-practice-a36f47b0b94c,

        [3]

        errors.Is: https://github.com/golang/go/blob/master/src/errors/wrap.go#L40,

        [4]

        errors.As example: https://github.com/golang/go/blob/master/src/errors/wrap_test.go#L255,

        [5]

        #45729: https://github.com/golang/go/issues/45729,

        [6]

        flysnow error 分析: https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html,

        [7]

        Nil is not nil: https://yourbasic.org/golang/gotcha-why-nil-error-not-equal-nil/,

        [8]

        errors are values: https://blog.golang.org/errors-are-values,



        推薦閱讀


        福利

        我為大家整理了一份從入門到進階的Go學習資料禮包,包含學習建議:入門看什么,進階看什么。關注公眾號 「polarisxu」,回復?ebook?獲??;還可以回復「進群」,和數(shù)萬 Gopher 交流學習。


        瀏覽 19
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久久久蜜桃 | 精工厂成人 免费网站观观看 | 国产精品久久久久久久免牛肉蒲团 | 亚洲精品一区二区三区婷婷月 | 欧美日韩国产综合在线 | 操 射 骚 fu | 精品欧美一区二区三区 | 日本黄色视频在线免费观看 | 免费看逼的网站 | asian艳丽少妇裸体魄pics |