上一个教程中,我们学习了如何在 Go 中表示错误以及如何处理标准库中的错误。我们还学习了如何从标准库错误中提取更多信息。

本教程介绍如何创建我们自己的自定义错误,我们可以在我们创建的函数和包中使用它们。我们还将使用标准库使用的相同技术来提供有关我们的自定义错误的更多详细信息。

使用新函数创建自定义错误

创建自定义错误的最简单方法是使用 errors 包的 New 函数。

在使用 New 函数创建自定义错误之前,让我们先了解它是如何实现的。下面提供了errors package 中 New 函数的实现。

  1. // Package errors implements functions to manipulate errors.
  2. package errors
  3. // New returns an error that formats as the given text.
  4. func New(text string) error {
  5. return &errorString{text}
  6. }
  7. // errorString is a trivial implementation of error.
  8. type errorString struct {
  9. s string
  10. }
  11. func (e *errorString) Error() string {
  12. return e.s
  13. }

实现非常简单。 errorString 是一个带有单个字符串字段 s 的结构类型。 第 14 行error 接口的 Error() string 方法是使用 errorString 指针接收器实现的。

New 函数在第 5 行接受一个 string 参数,使用该参数创建一个 errorString 类型的值,并返回它的地址。 因此一个新错误创建并返回了。

现在我们知道 New 函数的工作原理,让我们在自己的程序中使用它来创建自定义错误。

我们将创建一个计算圆的面积的简单程序,如果半径为负,则返回错误。

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "math"
  6. )
  7. func circleArea(radius float64) (float64, error) {
  8. if radius < 0 {
  9. return 0, errors.New("Area calculation failed, radius is less than zero")
  10. }
  11. return math.Pi * radius * radius, nil
  12. }
  13. func main() {
  14. radius := -20.0
  15. area, err := circleArea(radius)
  16. if err != nil {
  17. fmt.Println(err)
  18. return
  19. }
  20. fmt.Printf("Area of circle %0.2f", area)
  21. }

Run in playground

在上面的程序中,在第 10 行我们检查半径是否小于零。 如果是这样,我们返回零以及相应的错误消息。 如果半径大于 0,则在第 13 行计算面积并返回 nil 值的 error。

在 main 函数中,我们在第 19 行检查错误是否为 nil。 如果它不是零,我们打印错误并返回,否则输出面积。

在这个程序中,半径小于零,因此它将输出,

  1. Area calculation failed, radius is less than zero

使用Errorf向错误添加更多信息

上面的程序效果很好,但是如果我们输出导致错误的实际半径就不怎么 nice 了。于是fmt 软件包的 Errorf 函数派上用场的地方。此函数根据格式说明符格式化错误,并返回一个字符串作为满足错误的值。

让我们使用 Errorf 函数使程序变得更好。

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. func circleArea(radius float64) (float64, error) {
  7. if radius < 0 {
  8. return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
  9. }
  10. return math.Pi * radius * radius, nil
  11. }
  12. func main() {
  13. radius := -20.0
  14. area, err := circleArea(radius)
  15. if err != nil {
  16. fmt.Println(err)
  17. return
  18. }
  19. fmt.Printf("Area of circle %0.2f", area)
  20. }

Run in playground

在上面的程序中,Errorf 在第 10 行打印导致错误的实际半径。 运行此程序将输出,

  1. Area calculation failed, radius -20.00 is less than zero


使用结构类型和字段提供有关错误的更多信息

也可以使用结构类型来实现错误接口作为错误。这使我们在错误处理方面具有更大的灵活性。在我们的例子中,如果我们想访问导致错误的半径,现在唯一的方法是解析错误描述 Area calculation failed, radius -20.00 is less than zero。这样做并不合适,因为如果描述发生变化,我们的代码就会崩溃。

我们将使用前面教程“断言基础结构类型并从结构字段获取更多信息”一节中解释的标准库所遵循的策略,并使用结构字段提供对导致错误的半径的访问。我们将创建一个实现错误接口的 struct 类型,并使用它的字段提供有关错误的更多信息。

第一步是创建一个 struct 类型来表示错误。错误类型的命名约定是名称应该以文本Error 结尾。我们把struct类型命名为 areaError

  1. type areaError struct {
  2. err string
  3. radius float64
  4. }

上面的结构类型有一个字段 radius ,它存储负责错误的半径的值,err 字段存储实际的错误消息。

下一步是实现错误接口。

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

在上面的代码片段中,我们使用指针接收器 *areaError 实现错误接口的 Error() string 方法。此方法打印半径和错误描述。

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

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type areaError struct {
  7. err string
  8. radius float64
  9. }
  10. func (e *areaError) Error() string {
  11. return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
  12. }
  13. func circleArea(radius float64) (float64, error) {
  14. if radius < 0 {
  15. return 0, &areaError{"radius is negative", radius}
  16. }
  17. return math.Pi * radius * radius, nil
  18. }
  19. func main() {
  20. radius := -20.0
  21. area, err := circleArea(radius)
  22. if err != nil {
  23. if err, ok := err.(*areaError); ok {
  24. fmt.Printf("Radius %0.2f is less than zero", err.radius)
  25. return
  26. }
  27. fmt.Println(err)
  28. return
  29. }
  30. fmt.Printf("Area of rectangle1 %0.2f", area)
  31. }

Run in playground

在上面的程序中,第 17 行 circleArea 用于计算圆的面积。 此函数首先检查半径是否小于零,如果是,则使用导致错误的半径和相应的错误消息创建类型 areaError 的值,然后在 19 行返回它的地址以及 0 作为 area。 **因此,我们提供了关于错误的更多信息,在本例中,使用自定义错误结构的字段提供了导致错误的半径。**

如果半径不是负数,则此函数在第 21 行计算并返回该 area 以及 nil 错误。

main 函数的第 26 行,我们试图找到半径为 -20 的圆的 area 。 由于半径小于零,因此将返回错误。

在 27 行中我们检查错误是否为 nil,在下一行中我们断言它为 *areaError如果错误是 ***areaError 类型,我们使用 err.radius** 获取导致错误的半径,打印自定义错误消息并从程序返回。

如果断言失败,我们只需打印错误并返回。 如果没有错误,将打印该 area。

程序将输出

  1. Radius -20.00 is less than zero

现在让我们使用上一篇教程中描述的第二种策略,并使用定制错误类型的方法来提供有关错误的更多信息。

使用有关结构类型的方法提供有关错误的更多信息

在本节中,我们将编写一个计算矩形面积的程序。如果长度或宽度小于零,此程序将打印错误。

第一步是创建一个结构来表示错误。

  1. type areaError struct {
  2. err string //error description
  3. length float64 //length which caused the error
  4. width float64 //width which caused the error
  5. }

上面的错误结构类型包含一个错误描述字段以及导致错误的长度和宽度。

现在我们已经有了错误类型,让我们实现错误接口并在错误类型上添加几个方法来提供关于错误的更多信息。

  1. func (e *areaError) Error() string {
  2. return e.err
  3. }
  4. func (e *areaError) lengthNegative() bool {
  5. return e.length < 0
  6. }
  7. func (e *areaError) widthNegative() bool {
  8. return e.width < 0
  9. }

在上面的代码片段中,我们从 Error() string 方法返回错误的描述。 当 length 小于零时,lengthNegative() bool 方法返回 true,而当 width 小于零时,widthNegative() bool 方法返回 true。 **这两种方法提供了有关错误的更多信息,在这种情况下,它们表示面积计算是否因为长度为负或宽度为负而失败。 因此,我们使用 struct error 类型的方法来提供有关错误的更多信息。**

下一步是编写计算面积函数。

  1. func rectArea(length, width float64) (float64, error) {
  2. err := ""
  3. if length < 0 {
  4. err += "length is less than zero"
  5. }
  6. if width < 0 {
  7. if err == "" {
  8. err = "width is less than zero"
  9. } else {
  10. err += ", width is less than zero"
  11. }
  12. }
  13. if err != "" {
  14. return 0, &areaError{err, length, width}
  15. }
  16. return length * width, nil
  17. }

上面的 rectArea 函数检查长度或宽度是否小于零,如果是,则返回一条错误消息,否则返回以为 nil 的错误值和矩形面积。

让我们通过创建主函数来完成这个程序。

  1. func main() {
  2. length, width := -5.0, -9.0
  3. area, err := rectArea(length, width)
  4. if err != nil {
  5. if err, ok := err.(*areaError); ok {
  6. if err.lengthNegative() {
  7. fmt.Printf("error: length %0.2f is less than zero\n", err.length)
  8. }
  9. if err.widthNegative() {
  10. fmt.Printf("error: width %0.2f is less than zero\n", err.width)
  11. }
  12. return
  13. }
  14. fmt.Println(err)
  15. return
  16. }
  17. fmt.Println("area of rect", area)
  18. }

在main函数中,我们在第 4 行检查错误是否为 nil。 如果它不是 nil,我们断言它在下一行输入 *areaError。 然后使用 lengthNegative()widthNegative() 方法,检查错误是否是因为长度为负或宽度为负。 我们打印相应的错误消息并从程序返回。 因此,我们使用错误结构类型的方法来提供有关错误的更多信息。

如果没有错误,矩形的面积将被打印出来。

这是完整的程序供你参考。

  1. package main
  2. import "fmt"
  3. type areaError struct {
  4. err string //error description
  5. length float64 //length which caused the error
  6. width float64 //width which caused the error
  7. }
  8. func (e *areaError) Error() string {
  9. return e.err
  10. }
  11. func (e *areaError) lengthNegative() bool {
  12. return e.length < 0
  13. }
  14. func (e *areaError) widthNegative() bool {
  15. return e.width < 0
  16. }
  17. func rectArea(length, width float64) (float64, error) {
  18. err := ""
  19. if length < 0 {
  20. err += "length is less than zero"
  21. }
  22. if width < 0 {
  23. if err == "" {
  24. err = "width is less than zero"
  25. } else {
  26. err += ", width is less than zero"
  27. }
  28. }
  29. if err != "" {
  30. return 0, &areaError{err, length, width}
  31. }
  32. return length * width, nil
  33. }
  34. func main() {
  35. length, width := -5.0, -9.0
  36. area, err := rectArea(length, width)
  37. if err != nil {
  38. if err, ok := err.(*areaError); ok {
  39. if err.lengthNegative() {
  40. fmt.Printf("error: length %0.2f is less than zero\n", err.length)
  41. }
  42. if err.widthNegative() {
  43. fmt.Printf("error: width %0.2f is less than zero\n", err.width)
  44. }
  45. return
  46. }
  47. }
  48. fmt.Println("area of rect", area)
  49. }

Run in playground

这个程序将打印输出

  1. error: length -5.00 is less than zero
  2. error: width -9.00 is less than zero

我们已经看到了 error handling 教程中描述的三种方法中的两种方法的示例,以提供关于错误的更多信息。

第三种使用 direct comparison 的方法非常简单。我将把它作为一个练习留给你,让你了解如何使用这个策略来提供关于自定义错误的更多信息。

本教程到此结束。

下面简要回顾一下我们在本教程中学习的内容

  • 使用新的函数创建自定义错误


  • 使用errorf向错误添加更多信息


  • 使用结构类型提供关于错误的更多信息


  • 使用结构类型的方法提供关于错误的更多信息


原文链接

https://golangbot.com/custom-errors/