上一节我们讨论了Golan中如何表示错误,如何处理Golang标准库中的错误,以及如何获取更多的错误细节信息。
本节主要讨论下如何自定义错误类型,我们仍然基于上一节介绍的几种方式来提供错误细节信息。
通过errors.New()
函数来创建错误值
调用某个函数,比如filepath.Glob()
会返回错误,这里说的错误其实就是一个错误值。在Golang中,我们可以直接通过errors
包中的函数errors.New()
来创建一个错误值。New
函数的实现如下:
Copy // 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
定义了方法Error()
,因此也就实现了接口error
。注意这里定义方法Error()
时使用的是指针接收器,因此我们在New
方法中也返回了指针。
下面我们就使用New
函数来创建一个错误值(注意其实是个指针)。
我们实现一个程序来计算圆形面积,如果半径小于0,需要返回一个错误。
Copy 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)
}
函数circleArea
中,我们判断入参radius
是否小于0,如果是,则返回了一个通过errors.New()
创建的错误值,否则返回正常的计算结果。注意这里如果入参是否合我们都同时返回两个值,第一个参数表示面积,第二个参数表示错误值。
该程序执行结果如下:
Copy Area calculation failed, radius is less than zero
自定义结构体错误类型并通过其属性提供更多细节信息
Golang中所有错误类型均实现了error
接口,因此我们也可以自定义结构体类型,只要实现error
接口即可。我们可以将错误细节信息,比如前面例子中的radius
存在某个属性上。下面我们就来实现这样一个自定义结构体错误类型。
首先我们创建一个结构体类型,错误类型一般以Error
作为后缀,因此我们命名为areaError
。
Copy type areaError struct {
err string
radius float64
}
该结构体类型包含一个radius
属性,用来存放引起该错误的细节信息,即半径值。err
属性用来存放错误文本信息。
接着我们通过实现Error() string
方法来实现error
接口。
Copy func (e *areaError) Error() string {
return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}
注意我们使用的是指针接收器。下面我们实现main
函数来使用下该自定义错误类型。
Copy 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)
}
这里需要注意,由于我们定义的错误类型是按指针接收器实现的error
接口,因此我们在circleArea
函数中返回错误时需要返回areaError
结构体变量的指针。
我们在main
函数中去断言错误的类型,如果确实是*areaError
,则说明是我们定义的错误类型,因此该结构体变量有一个radius
属性来提供具体的不合法半径值。
自定义结构体错误类型并通过其方法提供更多细节信息
只要自定义结构体类型来实现error
接口,我们不仅可以通过某些属性来提供具体错误细节信息,也可以通过方法来提供一些错误细节。比如下面这个例子,用来计算长方形的面积。
Copy 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)
}
该程序比较简单,只是给自定义结构体错误类型多绑定了几个方法用来提供一些细节信息。该程序执行结果如下:
Copy error: length -5.00 is less than zero
error: width -9.00 is less than zero
基于变量比较也可以来提供一些错误信息,比较简单,这里就不举例子了,感兴趣的可以自己实现下。