Go语言自定义错误类型和错误处理

发布日期:

Go 程序使用 error 类型来表示错误状态。
error 类型是一个内建接口(interface)

type error interface {
    Error() string
}

你可以在自己的函数定义返回 error 类型,通常我们将函数或方法的最后一个返回值类型定义为 error

func Sqrt(f float64) (float64, error) {
    // example 1
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // example 2
    if f < 0 {
        return 0, fmt.Errorf("math: square root of negative number %g", f)
    }
}

在标准包内所有可能出错的API都会返回一个 error 变量

f, err := os.Open("/test.txt")
if err != nil {
    fmt.Println(err)
    return
}

你可以自定义 Error

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}
func (e *errorString) Error() string {
    return e.s
}
// errorString is a trivial implementation of error.
type errorString struct {
    s string
}
func (e *errorString) Error() string {
    return e.s
}

通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理。

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

error 为 nil 时表示成功;非 nil 的 error 表示失败。

自定义错误

go 语言的 error 是一个 interface ,这意味着我们可以通过自定义错误类型的方式去获取更多相关的错误信息

type PathError struct {
    Op   string
    Path string
    Err  error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.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)
}

上面代码得到的结果将是 Area calculation failed, radius is less than zero

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

运行结果 Area calculation failed, radius -20.00 is less than zero

自定义 error 结构类型

我们定义一个结构

type areaError struct {
    err    string    // 实际错误信息
    radius float64   // 半径
}

实现错误信息

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

我们编写 main 函数和 circleArea 函数来完成程序。

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

运行结果 Radius -20.00 is less than zero

使用类型断言的方式

完整程序

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(err)
        return
    }
    fmt.Println("area of rect", area)
}

运行结果

error: length -5.00 is less than zero
error: width -9.00 is less than zero

减少代码重复

Go语言中的错误处理采用的与C类似的检查返回值的方式,这样使得错误处理代码冗长且重复,例如如下所示

err := doStuff1()
if err != nil {
    //handle error...
}
err = doStuff2()
if err != nil {
    //handle error...
}
err = doStuff3()
if err != nil {
    //handle error...
}

通常,我们采用复用检测函数来减少类似代码,例如下面的 checkerror 的方式

func checkError(err error) {
    if err != nil {
        fmt.Println("Error is ", err)
        os.Exit(-1)
    }
}
func foo() {
    err := doStuff1()
    checkError(err)
    err = doStuff2()
    checkError(err)
    err = doStuff3()
    checkError(err)
}