欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一篇文章帶你輕松搞懂Golang的error處理

 更新時(shí)間:2022年07月11日 10:46:38   作者:三中門口賣烤冷面  
在進(jìn)行后臺(tái)開發(fā)的時(shí)候,錯(cuò)誤處理是每個(gè)程序員都會(huì)遇到的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于Golang中error處理的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

Golang中的error

Golang中的 error 就是一個(gè)簡(jiǎn)單的接口類型。只要實(shí)現(xiàn)了這個(gè)接口,就可以將其視為一種 error

type error interface {
    Error() string
}

error的幾種玩法

翻看Golang源碼,能看到許多類似于下面的這兩種error類型

哨兵錯(cuò)誤

var EOF = errors.New("EOF")
var ErrUnexpectedEOF = errors.New("unexpected EOF")
var ErrNoProgress = errors.New("multiple Read calls return no data or error")

缺點(diǎn):

1.讓 error 具有二義性

error != nil不再意味著一定發(fā)生了錯(cuò)誤
比如io.Reader返回io.EOF來(lái)告知調(diào)用者沒(méi)有更多數(shù)據(jù)了,然而這又不是一個(gè)錯(cuò)誤

2.在兩個(gè)包之間創(chuàng)建了依賴

如果你使用了io.EOF來(lái)檢查是否read完所有的數(shù)據(jù),那么代碼里一定會(huì)導(dǎo)入io包

自定義錯(cuò)誤類型

一個(gè)不錯(cuò)的例子是os.PathError,它的優(yōu)點(diǎn)是可以附帶更多的上下文信息

type PathError struct {
    Op   string
    Path string
    Err  error
}

Wrap error

到這里我們可以發(fā)現(xiàn),Golang 的 error 非常簡(jiǎn)單,然而簡(jiǎn)單也意味著有時(shí)候是不夠用的

Golang的error一直有兩個(gè)問(wèn)題:

1.error沒(méi)有附帶file:line信息(也就是沒(méi)有堆棧信息)

比如這種error,鬼知道代碼哪一行報(bào)了錯(cuò),Debug時(shí)簡(jiǎn)直要命

SERVICE ERROR 2022-03-25T16:32:10.687+0800!!!
       Error 1406: Data too long for column 'content' at row 1

2.上層error想附帶更多日志信息時(shí),往往會(huì)使用fmt.Errorf(),fmt.Errorf()會(huì)創(chuàng)建一個(gè)新的error,底層的error類型就被“吞”掉了

var errNoRows = errors.New("no rows")

// 模仿sql庫(kù)返回一個(gè)errNoRows
func sqlExec() error {
    return errNoRows
}

func serviceNoErrWrap() error {
    err := sqlExec()
    if err != nil {
        return fmt.Errorf("sqlExec failed.Err:%v", err)
    }
    
    return nil
}

func TestErrWrap(t *testing.T) {
    // 使用fmt.Errorf創(chuàng)建了一個(gè)新的err,丟失了底層err
    err := serviceNoErrWrap()
    if err != errNoRows {
        log.Println("===== errType don't equal errNoRows =====")
    }
}
-------------------------------代碼運(yùn)行結(jié)果----------------------------------
=== RUN   TestErrWrap
2022/03/26 17:19:43 ===== errType don't equal errNoRows =====

為了解決這個(gè)問(wèn)題,我們可以使用github.com/pkg/error包,使用errors.withStack()方法將err保
存到withStack對(duì)象

// withStack結(jié)構(gòu)體保存了error,形成了一條error鏈。同時(shí)*stack字段保存了堆棧信息。
type withStack struct {
    error
    *stack
}

也可以使用errors.Wrap(err, "自定義文本"),額外附帶一些自定義的文本信息

源碼解讀:先將err和message包進(jìn)withMessage對(duì)象,再將withMessage對(duì)象和堆棧信息包進(jìn)withStack對(duì)象

func Wrap(err error, message string) error {
    if err == nil {
        return nil
    }
    err = &withMessage{
        cause: err,
        msg:   message,
    }
    return &withStack{
        err,
        callers(),
    }
}

Golang1.13版本error的新特性

Golang1.13版本借鑒了github.com/pkg/error包,新增了如下函數(shù),大大增強(qiáng)了 Golang 語(yǔ)言判斷 error 類型的能力

errors.UnWrap()

// 與errors.Wrap()行為相反
// 獲取err鏈中的底層err
func Unwrap(err error) error {
    u, ok := err.(interface {
        Unwrap() error
    })
    if !ok {
        return nil
    }
    return u.Unwrap()
}

errors.Is()

在1.13版本之前,我們可以用err == targetErr判斷err類型
errors.Is()是其增強(qiáng)版:error 鏈上的任一err == targetErr,即return true

// 實(shí)踐:學(xué)習(xí)使用errors.Is()
var errNoRows = errors.New("no rows")

// 模仿sql庫(kù)返回一個(gè)errNoRows
func sqlExec() error {
    return errNoRows
}

func service() error {
    err := sqlExec()
    if err != nil {
        return errors.WithStack(err)    // 包裝errNoRows
    }
    
    return nil
}

func TestErrIs(t *testing.T) {
    err := service()
    
    // errors.Is遞歸調(diào)用errors.UnWrap,命中err鏈上的任意err即返回true
    if errors.Is(err, errNoRows) {
        log.Println("===== errors.Is() succeeded =====")
    }
    
    //err經(jīng)errors.WithStack包裝,不能通過(guò) == 判斷err類型
    if err == errNoRows {
        log.Println("err == errNoRows")
    }
}
-------------------------------代碼運(yùn)行結(jié)果----------------------------------
=== RUN   TestErrIs
2022/03/25 18:35:00 ===== errors.Is() succeeded =====

例子解讀:

因?yàn)槭褂?code>errors.WithStack包裝了sqlError,sqlError位于error鏈的底層,上層的error已經(jīng)不再是sqlError類型,所以使用==無(wú)法判斷出底層的sqlError

源碼解讀:

  • 我們很容易想到其內(nèi)部調(diào)用了err = Unwrap(err)方法來(lái)獲取error鏈中底層的error
  • 自定義error類型可以實(shí)現(xiàn)Is接口來(lái)自定義error類型判斷方法
func Is(err, target error) bool {
    if target == nil {
        return err == target
    }
    
    isComparable := reflectlite.TypeOf(target).Comparable()
    for {
        if isComparable && err == target {
            return true
        }
        // 支持自定義error類型判斷
        if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
            return true
        }
        if err = Unwrap(err); err == nil {
            return false
        }
    }
}

下面我們來(lái)看看如何自定義error類型判斷:

自定義的errNoRows類型,必須實(shí)現(xiàn)Is接口,才能使用erros.Is()進(jìn)行類型判斷

type errNoRows struct {
    Desc string
}

func (e errNoRows) Unwrap() error { return e }

func (e errNoRows) Error() string { return e.Desc }

func (e errNoRows) Is(err error) bool {
    return reflect.TypeOf(err).Name() == reflect.TypeOf(e).Name()
}

// 模仿sql庫(kù)返回一個(gè)errNoRows
func sqlExec() error {
    return &errNoRows{"Kaolengmian NB"}
}

func service() error {
    err := sqlExec()
    if err != nil {
        return errors.WithStack(err)
    }
    
    return nil
}

func serviceNoErrWrap() error {
    err := sqlExec()
    if err != nil {
        return fmt.Errorf("sqlExec failed.Err:%v", err)
    }
    
    return nil
}

func TestErrIs(t *testing.T) {
    err := service()
    
    if errors.Is(err, errNoRows{}) {
        log.Println("===== errors.Is() succeeded =====")
    }
}
-------------------------------代碼運(yùn)行結(jié)果----------------------------------
=== RUN   TestErrIs
2022/03/25 18:35:00 ===== errors.Is() succeeded =====

errors.As()

在1.13版本之前,我們可以用if _,ok := err.(targetErr)判斷err類型
errors.As()是其增強(qiáng)版:error 鏈上的任一err與targetErr類型相同,即return true

// 通過(guò)例子學(xué)習(xí)使用errors.As()
type sqlError struct {
    error
}

func (e *sqlError) IsNoRows() bool {
    t, ok := e.error.(ErrNoRows)
    return ok && t.IsNoRows()
}

type ErrNoRows interface {
    IsNoRows() bool
}

// 返回一個(gè)sqlError
func sqlExec() error {
    return sqlError{}
}

// errors.WithStack包裝sqlError
func service() error {
    err := sqlExec()
    if err != nil {
        return errors.WithStack(err)
    }
    
    return nil
}

func TestErrAs(t *testing.T) {
    err := service()
    
    // 遞歸使用errors.UnWrap,只要Err鏈上有一種Err滿足類型斷言,即返回true
    sr := &sqlError{}
    if errors.As(err, sr) {
        log.Println("===== errors.As() succeeded =====")
    }
    
    // 經(jīng)errors.WithStack包裝后,不能通過(guò)類型斷言將當(dāng)前Err轉(zhuǎn)換成底層Err
    if _, ok := err.(sqlError); ok {
        log.Println("===== type assert succeeded =====")
    }
}
----------------------------------代碼運(yùn)行結(jié)果--------------------------------------------
=== RUN   TestErrAs
2022/03/25 18:09:02 ===== errors.As() succeeded =====

例子解讀:

因?yàn)槭褂?code>errors.WithStack包裝了sqlError,sqlError位于error鏈的底層,上層的error已經(jīng)不再是sqlError類型,所以使用類型斷言無(wú)法判斷出底層的sqlError

error處理最佳實(shí)踐

上面講了如何定義error類型,如何比較error類型,現(xiàn)在我們談?wù)勅绾卧诖笮晚?xiàng)目中做好error處理

優(yōu)先處理error

當(dāng)一個(gè)函數(shù)返回一個(gè)非空error時(shí),應(yīng)該優(yōu)先處理error,忽略它的其他返回值

只處理error一次

  • 在Golang中,對(duì)于每個(gè)err,我們應(yīng)該只處理一次。
  • 要么立即處理err(包括記日志等行為),return nil(把錯(cuò)誤吞掉)。此時(shí)因?yàn)榘彦e(cuò)誤做了降級(jí),一定要小心處理函數(shù)返回值。

比如下面例子json.Marshal(conf)沒(méi)有return err ,那么在使用buf時(shí)一定要小心空指針等錯(cuò)誤

要么return err,在上層處理err

反例:

// 試想如果writeAll函數(shù)出錯(cuò),會(huì)打印兩遍日志
// 如果整個(gè)項(xiàng)目都這么做,最后會(huì)驚奇的發(fā)現(xiàn)我們?cè)谔幪幋蛉罩?,?xiàng)目中存在大量沒(méi)有價(jià)值的垃圾日志
// unable to write:io.EOF
// could not write config:io.EOF

type config struct {}

func writeAll(w io.Writer, buf []byte) error {
    _, err := w.Write(buf)
    if err != nil {
        log.Println("unable to write:", err)
        return err
    }
    
    return nil
}

func writeConfig(w io.Writer, conf *config) error {
    buf, err := json.Marshal(conf)
    if err != nil {
        log.Printf("could not marshal config:%v", err)
    }
    
    if err := writeAll(w, buf); err != nil {
        log.Println("count not write config: %v", err)
        return err
    }
    
    return nil
}

不要反復(fù)包裝error

我們應(yīng)該包裝error,但只包裝一次

上層業(yè)務(wù)代碼建議Wrap error,但是底層基礎(chǔ)Kit庫(kù)不建議

如果底層基礎(chǔ) Kit 庫(kù)包裝了一次,上層業(yè)務(wù)代碼又包裝了一次,就重復(fù)包裝了 error,日志就會(huì)打重

比如我們常用的sql庫(kù)會(huì)返回sql.ErrNoRows這種預(yù)定義錯(cuò)誤,而不是給我們一個(gè)包裝過(guò)的 error

不透明的錯(cuò)誤處理

在大型項(xiàng)目中,推薦使用不透明的錯(cuò)誤處理(Opaque errors):不關(guān)心錯(cuò)誤類型,只關(guān)心error是否為nil

好處:

耦合小,不需要判斷特定錯(cuò)誤類型,就不需要導(dǎo)入相關(guān)包的依賴。
不過(guò)有時(shí)候,這種處理error的方式不夠用,比如:業(yè)務(wù)需要對(duì)參數(shù)異常error類型做降級(jí)處理,打印Warn級(jí)別的日志

type ParamInvalidError struct {
    Desc string
}

func (e ParamInvalidError) Unwrap() error { return e }

func (e ParamInvalidError) Error() string { return "ParamInvalidError: " + e.Desc }

func (e ParamInvalidError) Is(err error) bool {
    return reflect.TypeOf(err).Name() == reflect.TypeOf(e).Name()
}

func NewParamInvalidErr(desc string) error {
    return errors.WithStack(&ParamInvalidError{Desc: desc})
}
------------------------------頂層打印日志---------------------------------
if errors.Is(err, Err.ParamInvalidError{}) {
    logger.Warnf(ctx, "%s", err.Error())
    return
}
if err != nil {
    logger.Errorf(ctx, " error:%+v", err)
}

簡(jiǎn)化錯(cuò)誤處理

Golang因?yàn)榇a中無(wú)數(shù)的if err != nil被詬病,現(xiàn)在我們看看如何減少if err != nil這種代碼

bufio.scan

CountLines() 實(shí)現(xiàn)了"讀取內(nèi)容的行數(shù)"功能

可以利用 bufio.scan() 簡(jiǎn)化 error 的處理:

func CountLines(r io.Reader) (int, error) {
    var (
        br    = bufio.NewReader(r)
        lines int
        err   error
    )
    
    for {
        _, err := br.ReadString('\n')
        lines++
        if err != nil {
            break
        }
    }
    
    if err != io.EOF {
        return 0, nilsadwawa 
    }
    
    return lines, nil
}

func CountLinesGracefulErr(r io.Reader) (int, error) {
    sc := bufio.NewScanner(r)
    
    lines := 0
    for sc.Scan() {
        lines++
    }
    
    return lines, sc.Err()
}

bufio.NewScanner() 返回一個(gè) Scanner 對(duì)象,結(jié)構(gòu)體內(nèi)部包含了 error 類型,調(diào)用Err()方法即可返回封裝好的error

Golang源代碼中蘊(yùn)含著大量的優(yōu)秀設(shè)計(jì)思想,我們?cè)陂喿x源碼時(shí)從中學(xué)習(xí),并在實(shí)踐中得以運(yùn)用

type Scanner struct {
    r            io.Reader // The reader provided by the client.
    split        SplitFunc // The function to split the tokens.
    maxTokenSize int       // Maximum size of a token; modified by tests.
    token        []byte    // Last token returned by split.
    buf          []byte    // Buffer used as argument to split.
    start        int       // First non-processed byte in buf.
    end          int       // End of data in buf.
    err          error     // Sticky error.
    empties      int       // Count of successive empty tokens.
    scanCalled   bool      // Scan has been called; buffer is in use.
    done         bool      // Scan has finished.
}

func (s *Scanner) Err() error {
    if s.err == io.EOF {
        return nil
    }
    return s.err
}

errWriter

WriteResponse()函數(shù)實(shí)現(xiàn)了"構(gòu)建HttpResponse"功能

利用上面學(xué)到的思路,我們可以自己實(shí)現(xiàn)一個(gè)errWriter對(duì)象,簡(jiǎn)化對(duì) error 的處理

type Header struct {
    Key, Value string
}

type Status struct {
    Code   int
    Reason string
}

func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
    _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
    if err != nil {
        return err
    }
    
    for _, h := range headers {
        _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)
        if err != nil {
            return err
        }
    }
    
    if _, err := fmt.Fprintf(w, "\r\n"); err != nil {
        return err
    }
    
    _, err = io.Copy(w, body)
    return err
}

type errWriter struct {
    io.Writer
    err error
}

func (e *errWriter) Write(buf []byte) (n int, err error) {
    if e.err != nil {
        return 0, e.err
    }
    
    n, e.err = e.Writer.Write(buf)
    
    return n, nil
}

func WriteResponseGracefulErr(w io.Writer, st Status, headers []Header, body io.Reader) error {
    ew := &errWriter{w, nil}
    
    fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
    
    for _, h := range headers {
        fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
    }
    
    fmt.Fprintf(w, "\r\n")
    
    io.Copy(ew, body)
    
    return ew.err
}

何時(shí)該用panic

在 Golang 中panic會(huì)導(dǎo)致程序直接退出,是一個(gè)致命的錯(cuò)誤。

建議發(fā)生致命的程序錯(cuò)誤時(shí)才使用 panic,例如索引越界、不可恢復(fù)的環(huán)境問(wèn)題、棧溢出等等

小補(bǔ)充

errors.New()返回的是errorString對(duì)象的指針,其原因是防止字符串產(chǎn)生碰撞,如果發(fā)生碰撞,兩個(gè) error 對(duì)象會(huì)相等。
源碼:

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
}

實(shí)踐:error1error2的text都是"error",但是二者并不相等

func TestErrString(t *testing.T) {
    var error1 = errors.New("error")
    var error2 = errors.New("error")
    
    if error1 != error2 {
        log.Println("error1 != error2")
    }
}
---------------------代碼運(yùn)行結(jié)果--------------------------
=== RUN   TestXXXX
2022/03/25 22:05:40 error1 != error2

參考文獻(xiàn)
《Effective GO》
《Go程序設(shè)計(jì)語(yǔ)言》
https://dave.cheney.net/practical-go/presentations/qcon-china.html#_error_handling

總結(jié) 

到此這篇關(guān)于Golang中error處理的文章就介紹到這了,更多相關(guān)Golang error處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • GO語(yǔ)言中回調(diào)函數(shù)的使用

    GO語(yǔ)言中回調(diào)函數(shù)的使用

    本文主要介紹了GO語(yǔ)言中回調(diào)函數(shù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Go中http超時(shí)問(wèn)題的排查及解決方法

    Go中http超時(shí)問(wèn)題的排查及解決方法

    這篇文章主要介紹了Go中http超時(shí)問(wèn)題的排查及解決方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-10-10
  • Go語(yǔ)言中Slice常見陷阱與避免方法詳解

    Go語(yǔ)言中Slice常見陷阱與避免方法詳解

    這篇文章主要為大家詳細(xì)介紹的是 Go 語(yǔ)言中的 Slice 的常見陷阱以及如何避免這些錯(cuò)誤,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以學(xué)習(xí)一下
    2023-02-02
  • 如何組織Go代碼目錄結(jié)構(gòu)依賴注入wire使用解析

    如何組織Go代碼目錄結(jié)構(gòu)依賴注入wire使用解析

    這篇文章主要為大家介紹了如何組織Go代碼目錄結(jié)構(gòu)依賴注入wire使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Go語(yǔ)言繼承功能使用結(jié)構(gòu)體實(shí)現(xiàn)代碼重用

    Go語(yǔ)言繼承功能使用結(jié)構(gòu)體實(shí)現(xiàn)代碼重用

    今天我來(lái)給大家介紹一下在?Go?語(yǔ)言中如何實(shí)現(xiàn)類似于繼承的功能,讓我們的代碼更加簡(jiǎn)潔和可重用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • golang 占位符和fmt常見輸出介紹

    golang 占位符和fmt常見輸出介紹

    這篇文章主要介紹了golang 占位符和fmt常見輸出介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • 淺談一下前端http與https有什么區(qū)別

    淺談一下前端http與https有什么區(qū)別

    這篇文章主要介紹了淺談一下前端http與https有什么區(qū)別,現(xiàn)今大部分的網(wǎng)站都已經(jīng)使用了 https 協(xié)議,那么https對(duì)比http協(xié)議有哪些不同呢,需要的朋友可以參考下
    2023-04-04
  • Go語(yǔ)言中你所不知道的位操作用法

    Go語(yǔ)言中你所不知道的位操作用法

    位運(yùn)算可能在平常的編程中使用的并不多,但涉及到底層優(yōu)化,一些算法及源碼可能會(huì)經(jīng)常遇見。下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中你所不知道的位操作用法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-12-12
  • Go 日志封裝實(shí)戰(zhàn)示例詳解

    Go 日志封裝實(shí)戰(zhàn)示例詳解

    這篇文章主要為大家介紹了Go 日志封裝實(shí)戰(zhàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Go?多環(huán)境下配置管理方案(多種方案)

    Go?多環(huán)境下配置管理方案(多種方案)

    這篇文章主要介紹了Go?多環(huán)境下配置管理方案,方案一配置文件管理,方案二集中式管理配置,每種方案給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06

最新評(píng)論