<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          萬字長文:Go error 處理最佳實(shí)踐

          共 10042字,需瀏覽 21分鐘

           ·

          2021-11-30 23:45

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

          什么是 error

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

          //?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 只有一個(gè)方法 Error() string 返回錯(cuò)誤消息

          //?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 時(shí)只需要調(diào)用 errors.New("error from somewhere") 即可,底層就是一個(gè)字符串結(jié)構(gòu)體 errorStrings

          當(dāng)前 error 有哪些問題

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

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

          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 模式更簡潔,一個(gè) ? 就代替了我們的操作。但是 error 的繁瑣判斷是當(dāng)前的痛點(diǎn)嘛?顯然不是,尤其喜歡 c 語言的人,反而喜歡每次都做判斷

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

          當(dāng)前 error 的問題有兩點(diǎn):

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

          1.Wrap 更多的消息

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

          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
          ????...
          */

          這是測試代碼,當(dāng)用 %v 打印時(shí)只有原始錯(cuò)誤信息,%+v 時(shí)打印完整調(diào)用棧。當(dāng) go1.13 后,標(biāo)準(zhǔn)庫 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
          }

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

          2.錯(cuò)誤類型

          這個(gè)例子來自 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

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

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

          但是如果 foo 把 error 做了一層 wrap 呢?這個(gè)時(shí)候錯(cuò)誤還是 sql.ErrNoRows 嘛?肯定不是,這點(diǎn)沒有 python try-catch 錯(cuò)誤處理強(qiáng)大,可以根據(jù)不同錯(cuò)誤 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
          */

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

          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] 判斷這個(gè) err 是否是 fs.PathError 類型,遞歸調(diào)用層層查找,源碼后面再講解

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

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

          在沒有 Is As 類型判斷時(shí),需要很惡心的去判斷錯(cuò)誤自符串

          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)絡(luò)接受到的連接錯(cuò)誤是 temporary 臨時(shí)的那么可以 continue 重試,當(dāng)然最好 backoff sleep 一下

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

          源碼實(shí)現(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, 代碼實(shí)現(xiàn)比較簡單,查看如何追蹤調(diào)用??梢圆榭丛创a

          2.github.com/pkg/errorsCause 實(shí)現(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)用,如果沒有實(shí)現(xiàn) causer 接口,那么就返回這個(gè) err

          3.官方庫如何生成一個(gè) wrapper error

          官方?jīng)]有這樣的函數(shù),而是 fmt.Errorf 格式化時(shí)使用 %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 時(shí)特殊處理 w, 使用 wrapError 封裝一下即可

          4.官方庫 Unwrap 實(shí)現(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 時(shí)生成的 error 結(jié)構(gòu)體如上所示,Unwrap 直接返回底層 err

          5.官方庫 Is As 實(shí)現(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一層層剝開,一個(gè)個(gè)比較,找到就返回true
          ?for?{
          ??if?isComparable?&&?err?==?target?{
          ???return?true
          ??}
          ??//這里意味著你可以自定義error的Is方法,實(shí)現(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必須是一個(gè)非nil指針
          ?if?typ.Kind()?!=?reflectlite.Ptr?||?val.IsNil()?{
          ??panic("errors:?target?must?be?a?non-nil?pointer")
          ?}
          ?
          ?//這里確保target是一個(gè)接口或者實(shí)現(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?{
          ?????//關(guān)鍵部分,反射判斷是否可被賦予,如果可以就賦值并且返回true
          ?????//本質(zhì)上,就是類型斷言,這是反射的寫法
          ??if?reflectlite.TypeOf(err).AssignableTo(targetType)?{
          ???val.Elem().Set(reflectlite.ValueOf(err))
          ???return?true
          ??}
          ??//這里意味著你可以自定義error的As方法,實(shí)現(xiàn)自己的類型斷言代碼
          ??if?x,?ok?:=?err.(interface{?As(interface{})?bool?});?ok?&&?x.As(target)?{
          ???return?true
          ??}
          ??//這里是遍歷error鏈的關(guān)鍵,不停的Unwrap,一層層的獲取err
          ??err?=?Unwrap(err)
          ?}
          ?return?false
          }

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

          ErrGroup 使用

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

          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, 原理非常簡單,代碼量非常少,感興趣的可以看源碼

          這里一定要注意三點(diǎn):

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

          線上實(shí)踐注意的幾個(gè)問題

          1.error 與 panic

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

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

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

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

          2.錯(cuò)誤處理與資源釋放

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

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

          這里要注意,main 函數(shù)中 done channel 千萬不能 close, 因?yàn)槟悴恢?doSomething 會超時(shí)多久返回,寫 closed channel 直接 panic

          所以這里有一個(gè)準(zhǔn)則:數(shù)據(jù)傳輸和退出控制,需要用單獨(dú)的 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)
          ?}
          }

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

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

          4.并發(fā)問題

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

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

          運(yùn)行之前,大家先猜下會發(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)
          ?......
          }

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

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

          6.errWriter

          本例來自官方 blog[8], 有時(shí)我們想做 pipeline 處理,需要把 err 當(dāng)成結(jié)構(gòu)體變量

          _,?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)
          }

          //?使用時(shí)
          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
          }

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

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

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

          我們項(xiàng)目的使用情況是 log error 級別的打印棧,warn 和 info 都不打印,當(dāng)然 case by case 還得看實(shí)際使用情況

          8.Wrap前做判斷

          errors.Wrap(err,?"failed")

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

          小結(jié)

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

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

          關(guān)于 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,



          推薦閱讀


          福利

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


          瀏覽 19
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  射一身成人影视 | 亚洲爆乳一区二区三区 | 国内毛片毛片毛片毛片毛片毛片毛片 | 国产又粗又长又黄又爽网站 | 久久四虎影院 |