- 原文地址:Part 31: Custom Errors
- 原文作者:Naveen R
- 譯者:咔嘰咔嘰 轉載請註明出處。
在上一個教程中,我們學習了在 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)
}
複製程式碼
在上面的程式中,我們在第 10 行檢查半徑是否小於零。如果是,則返回 0 以及相應的error
內容。在第 13 行中,如果半徑大於 0,則計算面積並返回值為nil
的error
。
在 main 函式中,我們在第 19 行檢查error
是否為nil
。如果不是nil
,就列印錯誤並返回,否則列印區域的面積。
在這個程式中,半徑小於零,因此它將列印,
Area calculation failed, radius is less than zero
複製程式碼
使用Errorf
為error
增加更多的資訊
上面的程式效果還不錯,但是如果我們要想列印確切的錯誤資訊就有點吃力了。這種場景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)
}
複製程式碼
在上面的程式中,第 10 行使用了Errorf
, 列印導致錯誤的實際半徑值,執行此程式將輸出,
Area calculation failed, radius -20.00 is less than zero
複製程式碼
使用結構型別和欄位提供有關error
的更多資訊
也可以使用實現錯誤介面的結構型別作為error
。這為我們提供了更多的靈活性。在我們的示例中,如果我們想要檢視錯誤,唯一的方法是解析Area calculation failed, radius -20.00 is less than zero
。這不是一種合適的方法,因為如果描述發生變化,我們的程式碼邏輯就得修改。
我們將使用前一個教程中所提到的“斷言結構型別並從結構型別的欄位中獲取更多資訊”方式,使用結構欄位來提供err
和radius
。我們將建立一個實現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
方法。該方法列印radius
和err
描述。
讓我們通過編寫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)
}
複製程式碼
在上面的程式中,第 17 行的circleArea
用於計算圓的面積。此函式首先檢查半徑是否小於零,如果是,則使用該錯誤的半徑和相應的錯誤內容描述去建立areaError
,然後在第 19 行返回areaError
的地址和錯誤內容。因此,我們提供了更多的error
資訊,在這個例子中,使用error
結構的欄位實現了自定義。???
如果半徑不是負數,則此函式計算並返回面積和nil
在第 26 行的main
函式中,我們試圖計算半徑為-20 的圓面積。由於半徑小於零,因此將返回錯誤。
我們在第 27 行檢查err
是否為nil
,並在下一行中斷言err
是* areaError
型別。如果是* areaError型別
,我們在 29 行使用err.radius
獲取造成error
的radius
,然後列印自定義錯誤內容並從程式返回。
輸出,
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
。然後使用lengthNegative
和widthNegative
方法,檢查錯誤是否是因為長度為負或寬度為負。我們列印相應的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
的更多資訊。