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

https://juejin.im/post/5ca1ad13e51d4555c5453899

在上一个教程中,我们学习了在 Go 语言中的error是如何表示的以及怎么用标准库处理error。我们还学习了如何从标准库中提取更多的error信息。

本教程介绍如何在我们的函数和包中使用自己定义的error,我们将使用标准库使用的相同技术来提供有关我们的自定义error的更多信息。

使用New函数创建自定义error

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

在我们使用New函数创建自定义error之前,我们先来了解它是如何实现的。下面提供了errors 包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的结构类型。error接口的Error方法是在第 14 行使用errorString指针接收器实现的。

第 5 行的New函数接受一个字符串参数,使用该参数创建一个errorString类型的值并返回它的地址,一个新的error就完成了。

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

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

  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 以及相应的error内容。在第 13 行中,如果半径大于 0,则计算面积并返回值为nilerror

在 main 函数中,我们在第 19 行检查error是否为nil。如果不是nil,就打印错误并返回,否则打印区域的面积。

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

  1. Area calculation failed, radius is less than zero

使用Errorferror增加更多的信息

上面的程序效果还不错,但是如果我们要想打印确切的错误信息就有点吃力了。这种场景fmt包的Errorf函数就派上用场了。此函数使用字符串格式化参数,并返回一个字符串作为error的值。

让我们来试试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

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

  1. 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

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

上面的结构类型有一个字段radius,它存储了负责该error的半径值,而err字段则存储了实际的错误内容。

下一步是实现error接口。

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

在上面的代码片段中,我们使用指针接收者* areaError实现错误接口的Error方法。该方法打印radiuserr描述。

让我们通过编写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 playgroud

在上面的程序中,第 17 行的circleArea用于计算圆的面积。此函数首先检查半径是否小于零,如果是,则使用该错误的半径和相应的错误内容描述去创建areaError,然后在第 19 行返回areaError的地址和错误内容。因此,我们提供了更多的error信息,在这个例子中,使用error结构的字段实现了自定义。

如果半径不是负数,则此函数计算并返回面积和nil

在第 26 行的main函数中,我们试图计算半径为-20 的圆面积。由于半径小于零,因此将返回错误。

我们在第 27 行检查err是否为nil,并在下一行中断言err* areaError类型。如果是* areaError类型,我们在 29 行使用err.radius获取造成errorradius,然后打印自定义错误内容并从程序返回。

输出,

  1. Radius -20.00 is less than zero

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

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

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

第一步是创建一个表示error的结构。

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

上面的错误结构类型包含错误描述字段err以及长度length和宽度width

现在我们已经定义好了error类型,让我们实现error接口并在类型上添加几个方法以提供有关的更多信息。

  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方法返回错误的描述e.err。当length小于 0 时,lengthNegative方法返回true,而当width小于 0 时,widthNegative方法返回true。这两种方法提供了有关信息,在这种情况下,它们表示区域计算是否因为长度为负或宽度为负而失败。因此,我们使用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函数检查长度或宽度是否小于 0,如果是,则返回 0 和error,否则返回矩形面积和nil。 让我们通过创建main函数来完成整个程序。

  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函数中,我们检查err是否为nil。 如果它不是nil,我们在下一行断言* areaError。然后使用lengthNegativewidthNegative方法,检查错误是否是因为长度为负或宽度为负。我们打印相应的error内容并从程序返回。因此,我们使用结构类型的方法来提供有关的更多信息。

如果没有错误,将打印矩形面积。

最后贴出完整的程序供参考。

  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处理教程中描述的三种方法中的两种的示例,以提供有关的更多信息。

第三种方式使用直接比较的方法比较简单。我会留下它作为练习,让您弄清楚如何使用此策略来提供有关我们的自定义error的更多信息。

ft_authoradmin  ft_create_time2019-08-03 16:41
 ft_update_time2019-08-03 16:41