[译] Part 31: golang 中的自定义 error

·  阅读 8263

在上一个教程中,我们学习了在 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的更多信息。

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改