[譯] Part 31: Golang 中的自定義Error

咔嘰咔嘰發表於2019-04-01

在上一個教程中,我們學習了在 Go 語言中的error是如何表示的以及怎麼用標準庫處理error。我們還學習瞭如何從標準庫中提取更多的error資訊。

本教程介紹如何在我們的函式和包中使用自己定義的error,我們將使用標準庫使用的相同技術來提供有關我們的自定義error的更多資訊。

使用New函式建立自定義error

建立自定義error最簡單的方法是使用errors包的New函式。

在我們使用New函式建立自定義error之前,我們先來了解它是如何實現的。下面提供了errors 包New函式的實現。

// 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
  }
複製程式碼

實現非常簡單。errorString是一個帶有單個字串s的結構型別。error介面的Error方法是在第 14 行使用errorString指標接收器實現的。

第 5 行的New函式接受一個字串引數,使用該引數建立一個errorString型別的值並返回它的地址,一個新的error就完成了。

現在我們知道了New函式的工作原理,讓我們在自己的程式中使用它來建立自定義error

我們將建立一個計算圓的面積的簡單程式,如果半徑為負,則返回error

package main

import (
    "errors"
    "fmt"
    "math"
)

func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, errors.New("Area calculation failed, radius is less than zero")
    }
    return math.Pi * radius * radius, nil
}

func main() {
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}
複製程式碼

Run in playground

在上面的程式中,我們在第 10 行檢查半徑是否小於零。如果是,則返回 0 以及相應的error內容。在第 13 行中,如果半徑大於 0,則計算面積並返回值為nilerror

在 main 函式中,我們在第 19 行檢查error是否為nil。如果不是nil,就列印錯誤並返回,否則列印區域的面積。

在這個程式中,半徑小於零,因此它將列印,

Area calculation failed, radius is less than zero
複製程式碼

使用Errorferror增加更多的資訊

上面的程式效果還不錯,但是如果我們要想列印確切的錯誤資訊就有點吃力了。這種場景fmt包的Errorf函式就派上用場了。此函式使用字串格式化引數,並返回一個字串作為error的值。

讓我們來試試Errorf函式,

package main

import (
    "fmt"
    "math"
)

func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
    }
    return math.Pi * radius * radius, nil
}

func main() {
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}
複製程式碼

Run in playground

在上面的程式中,第 10 行使用了Errorf, 列印導致錯誤的實際半徑值,執行此程式將輸出,

Area calculation failed, radius -20.00 is less than zero
複製程式碼

使用結構型別和欄位提供有關error的更多資訊

也可以使用實現錯誤介面的結構型別作為error。這為我們提供了更多的靈活性。在我們的示例中,如果我們想要檢視錯誤,唯一的方法是解析Area calculation failed, radius -20.00 is less than zero。這不是一種合適的方法,因為如果描述發生變化,我們的程式碼邏輯就得修改。

我們將使用前一個教程中所提到的“斷言結構型別並從結構型別的欄位中獲取更多資訊”方式,使用結構欄位來提供errradius。我們將建立一個實現error介面的結構型別,並使用其欄位提供有關的更多資訊。

第一步是建立一個結構型別來表示error。型別的命名約定是Error結尾。所以我們將結構型別命名為areaError

type areaError struct {
    err    string
    radius float64
}
複製程式碼

上面的結構型別有一個欄位radius,它儲存了負責該error的半徑值,而err欄位則儲存了實際的錯誤內容。

下一步是實現error介面。

func (e *areaError) Error() string {
    return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}
複製程式碼

在上面的程式碼片段中,我們使用指標接收者* areaError實現錯誤介面的Error方法。該方法列印radiuserr描述。

讓我們通過編寫main函式和circleArea函式來完成程式。

package main

import (
    "fmt"
    "math"
)

type areaError struct {
    err    string
    radius float64
}

func (e *areaError) Error() string {
    return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}

func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, &areaError{"radius is negative", radius}
    }
    return math.Pi * radius * radius, nil
}

func main() {
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            fmt.Printf("Radius %0.2f is less than zero", err.radius)
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of rectangle1 %0.2f", area)
}
複製程式碼

Run in playgroud

在上面的程式中,第 17 行的circleArea用於計算圓的面積。此函式首先檢查半徑是否小於零,如果是,則使用該錯誤的半徑和相應的錯誤內容描述去建立areaError,然後在第 19 行返回areaError的地址和錯誤內容。因此,我們提供了更多的error資訊,在這個例子中,使用error結構的欄位實現了自定義。???

如果半徑不是負數,則此函式計算並返回面積和nil

在第 26 行的main函式中,我們試圖計算半徑為-20 的圓面積。由於半徑小於零,因此將返回錯誤。

我們在第 27 行檢查err是否為nil,並在下一行中斷言err* areaError型別。如果是* areaError型別,我們在 29 行使用err.radius獲取造成errorradius,然後列印自定義錯誤內容並從程式返回。

輸出,

Radius -20.00 is less than zero
複製程式碼

現在讓我們使用上一個教程中描述的第二個策略,並使用自定義錯誤型別的方法來提供有關error的更多資訊。

使用結構型別的方法提供有關error的更多資訊

在本節中,我們將編寫一個計算矩形區域的程式。如果長度或寬度小於 0,該程式將列印錯誤。

第一步是建立一個表示error的結構。

type areaError struct {
    err    string //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}
複製程式碼

上面的錯誤結構型別包含錯誤描述欄位err以及長度length和寬度width

現在我們已經定義好了error型別,讓我們實現error介面並在型別上新增幾個方法以提供有關的更多資訊。

func (e *areaError) Error() string {
    return e.err
}

func (e *areaError) lengthNegative() bool {
    return e.length < 0
}

func (e *areaError) widthNegative() bool {
    return e.width < 0
}
複製程式碼

在上面的程式碼片段中,我們從Error方法返回錯誤的描述e.err。當length小於 0 時,lengthNegative方法返回true,而當width小於 0 時,widthNegative方法返回true。這兩種方法提供了有關資訊,在這種情況下,它們表示區域計算是否因為長度為負或寬度為負而失敗。因此,我們使用error結構型別的方法來提供有關的更多資訊。

下一步是實現面積計算功能。

func rectArea(length, width float64) (float64, error) {
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"
        }
    }
    if err != "" {
        return 0, &areaError{err, length, width}
    }
    return length * width, nil
}
複製程式碼

上面的rectArea函式檢查長度或寬度是否小於 0,如果是,則返回 0 和error,否則返回矩形面積和nil。 讓我們通過建立main函式來完成整個程式。

func main() {
    length, width := -5.0, -9.0
    area, err := rectArea(length, width)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            if err.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zero\n", err.length)

            }
            if err.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zero\n", err.width)

            }
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println("area of rect", area)
}
複製程式碼

main函式中,我們檢查err是否為nil。 如果它不是nil,我們在下一行斷言* areaError。然後使用lengthNegativewidthNegative方法,檢查錯誤是否是因為長度為負或寬度為負。我們列印相應的error內容並從程式返回。因此,我們使用結構型別的方法來提供有關的更多資訊。

如果沒有錯誤,將列印矩形面積。

最後貼出完整的程式供參考。

package main

import "fmt"

type areaError struct {
    err    string  //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}

func (e *areaError) Error() string {
    return e.err
}

func (e *areaError) lengthNegative() bool {
    return e.length < 0
}

func (e *areaError) widthNegative() bool {
    return e.width < 0
}

func rectArea(length, width float64) (float64, error) {
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"
        }
    }
    if err != "" {
        return 0, &areaError{err, length, width}
    }
    return length * width, nil
}

func main() {
    length, width := -5.0, -9.0
    area, err := rectArea(length, width)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            if err.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zero\n", err.length)

            }
            if err.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zero\n", err.width)

            }
            return
        }
    }
    fmt.Println("area of rect", area)
}
複製程式碼

Run in playground 程式輸出,

error: length -5.00 is less than zero
error: width -9.00 is less than zero
複製程式碼

我們已經看到了error處理教程中描述的三種方法中的兩種的示例,以提供有關的更多資訊。

第三種方式使用直接比較的方法比較簡單。我會留下它作為練習,讓您弄清楚如何使用此策略來提供有關我們的自定義error的更多資訊。

相關文章