[关闭]
@phper 2018-03-13T10:07:44.000000Z 字数 6128 阅读 307

31.自定义错误

Golang

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


欢迎访问Golang 系列教程中的第31章。

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

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

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

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

在使用New函数创建自定义错误之前, 让我们了解它是如何实现的。下面提供了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() string方法是使用errorString 指针接收器在第14行。

第5行中的New函数接受一个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. }

在playground上运行

在上面的程序中, 我们在第10行检查半径是否小于0。如如果少于0, 我们返回零的面积连同相应的错误消息。如果半径大于0, 则计算该区域, 并在第13行中返回nil错误。

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

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

Area calculation failed, radius is less than zero  

使用Errorf函数将更多信息添加到错误中

上述程序运行良好,但如果我们打印引起错误的实际半径,这不是很好。这就是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. }

在playground上运行

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

Area calculation failed, radius -20.00 is less than zero  

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

也可以使用实现error接口结构类型作为错误。这为我们提供了更多的错误处理灵活性。在我们的例子中,如果我们想要访问导致错误的半径,现在唯一的办法是解析错误描述Area calculation failed, radius -20.00 is less than zero.这不是一个适当的方式来做到这一点,因为如果描述改变, 我们的代码将会奔溃。

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

第一步是创建结构类型来表示错误。错误类型的命名约定是名称应以文本Error结尾。因此, 让我们把我们的结构类型命名为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. }

在操场上奔跑

在上面的程序中, 17 行中的circleArea用于计算圆的面积。此函数首先检查半径是否小于零, 如果这样, 它将使用负责错误的半径和相应的错误信息创建areaError类型的值, 然后以0的形式返回19行中的地址 (以地区.因此, 我们提供了有关错误的更多信息, 在这种情况下, 使用自定义错误结构的字段导致错误的半径.

如果半径不是负值, 则此函数计算并返回21行中的区域以及nil错误。

在26行的主函数中, 我们试图找到一个半径为20的圆的面积。由于半径小于零, 将返回一个错误。

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

如果断言失败, 我们只需打印32行的错误并返回。如果没有错误, 该区域将打印在35行。

程序将打印,

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方法中的错误描述。当长度小于零且widthNegative() bool方法在宽度小于零时返回 true 时, lengthNegative() bool方法返回 true。这两种方法提供了有关错误的更多信息, 在这种情况下, 他们会说区域计算是否因长度为负值或宽度为负值而失败。因此, 我们使用了结构错误类型的方法来提供有关错误的更多信息.

下一步是编写区域计算函数。

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

在主函数中, 我们检查4行的错误是否为零。如果它不是零, 我们断言它在下一行中键入*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. fmt.Println(err)
  48. return
  49. }
  50. fmt.Println("area of rect", area)
  51. }

在操场上奔跑

该程序将打印输出,

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

我们看到了错误处理教程中描述的三种方法中的两种示例, 以提供有关错误的更多信息。

使用直接比较的第三种方法非常简单。我将把它作为一个练习, 让你弄清楚如何使用这个策略来提供关于我们的自定义错误的更多信息。

这使我们结束了本教程。

下面是我们在本教程中所学到的一个快速回顾,

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注