自定义错误
在上一个教程中,我们学习了如何在Go中表示错误以及如何处理标准库中的错误。 我们还学习了如何从标准库错误中提取更多信息。
本教程介绍如何创建我们自己的自定义错误,我们可以在我们创建的函数和包中使用它们。 我们还将使用标准库使用的相同技术来提供有关我们的自定义错误的更多详细信息。
使用New函数创建自定义错误
创建自定义错误的最简单方法是使用错误包的New
函数。
在我们使用New函数创建自定义错误之前,让我们了解它是如何实现的。 下面提供了错误包中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() string
方法是使用第14行中的errorString
指针接收器实现的。
第5行的New
函数,接受一个字符串参数,使用该参数创建一个类型为errorString
的值,并返回它的地址。 因此,创建并返回新错误。
现在我们知道了New
函数的工作原理,让我们在自己的程序中使用它来创建自定义错误。
我们将创建一个计算圆的面积的简单程序,如果半径为负,则返回错误。
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,则计算面积并返回nil
作为第13行中的错误。
在main
函数中,我们检查错误是否为nil,我们打印错误并返回,否则打印圆面积。
在这个程序中,半径小于零,因此它将打印,
Area calculation failed, radius is less than zero
使用Errorf向错误添加更多信息
上面的程序效果很好,但是如果我们打印导致错误的实际半径就不会很好。 这是fmt
软件包的Errorf
函数派上用场的地方。此函数根据格式说明符格式化错误,并返回一个字符串作为满足错误的值。
让我们使用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)
}
在上面的程序中,Errorf
用于打印导致错误的实际半径。 运行此程序将输出,
Area calculation failed, radius -20.00 is less than zero
使用结构类型和字段提供有关错误的更多信息
也可以使用实现错误接口的结构类型作为错误。这为我们提供了更多的错误处理灵活性。在我们的示例中,如果我们想要访问导致错误的半径,现在唯一的方法是解析错误描述Area calculation failed, radius -20.00 is less than zero
.。这不是一种正确的方法,因为如果描述发生变化,我们的代码就会中断。
我们将使用前面教程中的“断言底层结构类型并从结构域中获取更多信息”部分中解释的标准库所遵循的策略,并使用struct字段来提供对导致错误的半径的访问。我们将创建一个实现错误接口的结构类型,并使用其字段提供有关错误的更多信息。
第一步是创建一个结构类型来表示错误。错误类型的命名约定是名称应以文本Error
结尾。所以我们将结构类型命名为areaError
type areaError struct {
err string
radius float64
}
上面的结构类型有一个字段radius
,它存储负责该错误的半径值,而err字段存储实际的错误消息。
下一步是实现error
接口。
func (e *areaError) Error() string {
return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}
在上面的代码片段中,我们使用指针接收器* areaError
实现错误接口的Error() string
方法。 此方法打印半径和错误描述。
让我们通过编写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)
}
在上面的程序中,circleArea
用于计算圆的面积。此函数首先检查半径是否小于零,如果是,则使用负责错误的半径和相应的错误消息创建类型areaError
的值,然后在行第19行中返回它的地址。因此,我们提供了有关错误的更多信息,在这种情况下,使用自定义错误结构的字段描述错误的半径。
在主函数第26行中,我们试图找到半径为-20的圆的面积。由于半径小于零,将返回错误。
我们检查错误是否存在,并在下一行中断言它的类型* areaError
。如果错误的类型为* areaError
,则会得到导致第29行使用err.radius
为0的半径,打印自定义错误消息并从程序返回。
如果断言失败,我们只需在第32行中打印错误。如果没有错误,该面积将在第35行打印。
该程序将打印,
Radius -20.00 is less than zero
现在让我们使用上一个教程中描述的第二个策略,并使用自定义错误类型的方法来提供有关错误的更多信息。
使用结构类型的方法提供有关错误的更多信息
在本节中,我们将编写一个计算矩形面积的程序。 如果长度或宽度小于零,该程序将打印错误。
第一步是创建一个表示错误的结构。
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
}
在上面的代码片段中,我们从 Error() string
方法返回错误的描述。 当length小于零时,lengthNegative() bool
方法返回true,而当width小于零时,widthNegative() bool
方法返回true。 这两种方法提供了有关错误的更多信息,在这种情况下,它们表示区域计算是否因为长度为负或宽度为负而失败。 因此,我们使用struct 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
函数检查长度或宽度是否小于零,如果是,则返回错误消息,否则返回矩形面积,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函数中,我们检查错误是否为nil,我们断言它在下一行输入* areaError
。 然后使用lengthNegative()
和widthNegative()
方法,检查错误是否是因为长度为负或宽度为负。 我们打印相应的错误消息并从程序返回。 因此,我们使用错误结构类型的方法来提供有关错误的更多信息。
如果没有错误,将打印矩形面积。
这是完整的程序供您参考。
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(err)
return
}
fmt.Println("area of rect", area)
}
该程序将打印输出,
error: length -5.00 is less than zero
error: width -9.00 is less than zero
我们已经看到了错误处理教程中描述的三种方法中的两种的示例,以提供有关错误的更多信息。
使用直接比较的第三种方式非常简单。 我会留下它作为练习,让您弄清楚如何使用此策略提供有关我们的自定义错误的更多信息。
这使我们结束本教程。
以下是我们在本教程中学到的内容的快速回顾,
-
使用New函数创建自定义错误
-
使用Errorf向错误添加更多信息
-
使用结构类型和字段提供有关错误的更多信息
-
使用结构类型的方法提供有关错误的更多信息