自定义错误
Q7nl1s admin

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

本教程涉及到如何创建我们自己的自定义错误,我们可以在我们的函数和包中使用。我们还将使用标准库所采用的相同技术来提供更多关于我们自定义错误的细节。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package errors

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
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是一个结构类型,有一个单一的字符串字段serror接口的Error() string方法是在第14行使用errorString指针接收器实现的。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)
}

在操场上运行

在上述程序中,我们在第10行检查半径是否小于0。如果是的话,我们就返回面积为零,并给出相应的错误信息。如果半径大于0,则计算出面积,并在第13行返回 “nil “作为错误。13.

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

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

1
Area calculation failed, radius is less than zero  

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

上述程序运行良好,但如果我们打印出导致错误的实际半径,那不是更好吗?这就是fmt包的Errorf函数的用武之地。这个函数根据一个格式指定器来格式化错误,并返回一个满足error interface的字符串作为值。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
}

在操场上运行

在上面的程序中,Errorf在第10行被调用,来打印导致error的实际半径。运行这个程序会输出。

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

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

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

我们将使用上一篇教程中 “将错误转换为底层类型并从结构字段中检索更多信息 “一节中解释的标准库所遵循的策略,并使用结构字段来提供对导致error的半径的访问。我们将创建一个实现错误接口的结构类型,并使用其字段来提供关于错误的更多信息。

第一步将是创建一个结构类型来表示错误。错误类型的命名惯例是,名称应以文本Error结尾。因此,我们将结构类型命名为 areaError

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

上述结构类型有一个字段radius,存储负责错误的radius的值,err字段存储实际的错误信息。

下一步是实现错误接口。

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

在上面的切片中,我们使用一个指针接收器*areaError来实现错误接口的Error() string方法。这个方法打印出radius和错误描述。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"errors"
"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{
err: "radius is negative",
radius: radius,
}
}
return math.Pi * radius * radius, nil
}

func main() {
radius := -20.0
area, err := circleArea(radius)
if err != nil {
var areaError *areaError
if errors.As(err, &areaError) {
fmt.Printf("Area calculation failed, radius %0.2f is less than zero", areaError.radius)
return
}
fmt.Println(err)
return
}
fmt.Printf("Area of rectangle %0.2f", area)
}

在操场上运行

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

如果半径不是负值,该函数将计算并返回面积,同时在第25行返回一个空的错误。

main函数的第30行中,我们试图找出半径为-20的圆的面积。由于半径小于零,将返回一个错误。

我们在第31行检查该错误是否为零,在第33行,我们尝试将其转换为*areaError类型。如果错误是*areaError类型的,我们在第34行使用areaError.radius得到导致错误的半径,以打印一个自定义的错误信息,然后从程序中返回。

如果错误不属于*areaError类型,我们只需在第37行打印错误信息并返回。如果没有错误,area将被打印在第40行。

程序将打印出来。

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

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

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
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()字符串方法中返回错误的描述。lengthNegative() bool方法在长度小于0时返回truewidthNegative() bool方法在宽度小于0时返回true。这两个方法提供了更多关于错误的信息,在这种情况下,它们说明面积计算失败是因为长度为负数或宽度为负数。因此,我们使用了结构错误类型的方法来提供更多的错误信息。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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: err,
length: length,
width: width,
}
}
return length * width, nil
}

上面的rectArea函数检查长度或宽度是否小于0,如果是,它返回一个*areaError类型的错误,否则它返回矩形的面积,err赋值nil

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {  
length, width := -5.0, -9.0
area, err := rectArea(length, width)
if err != nil {
var areaError *areaError
if errors.As(err, &areaError) {
if areaError.lengthNegative() {
fmt.Printf("error: length %0.2f is less than zero\n", areaError.length)

}
if areaError.widthNegative() {
fmt.Printf("error: width %0.2f is less than zero\n", areaError.width)

}
return
}
fmt.Println(err)
return
}
fmt.Println("area of rect", area)
}

main函数中,我们在第4行检查错误是否为nil。如果它不是nil,我们尝试将其转换为*areaError类型。然后使用lengthNegative()widthNegative()方法,我们检查错误是否是由于长度为负数或宽度为负数的事实。我们打印相应的错误信息并从程序中返回。因此,我们使用了错误结构类型的方法来提供关于错误的更多信息。

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

下面是完整的程序供你参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package main

import (
"errors"
"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: err,
length: length,
width: width,
}
}
return length * width, nil
}

func main() {
length, width := -5.0, -9.0
area, err := rectArea(length, width)
if err != nil {
var areaError *areaError
if errors.As(err, &areaError) {
if areaError.lengthNegative() {
fmt.Printf("error: length %0.2f is less than zero\n", areaError.length)

}
if areaError.widthNegative() {
fmt.Printf("error: width %0.2f is less than zero\n", areaError.width)

}
return
}
fmt.Println(err)
return
}
fmt.Println("area of rect", area)
}

在操场上运行

这个程序将打印输出。

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

我们已经看到了错误处理教程中描述的三种方式中的两种的例子,以提供更多的错误信息。

第三种使用直接比较的方式。我想把它作为一个练习留给你,让你找出如何使用这个策略来提供关于我们的自定义错误的更多信息。

这就给我们带来了本教程的结束。

下面是对本教程中所学内容的快速回顾。

  • 使用New函数创建自定义错误
  • 使用Errorf为错误添加更多信息
  • 使用结构类型和字段提供更多关于错误的信息
  • 使用结构类型的方法提供更多关于错误的信息

祝你有个愉快的一天。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View