引言
当Go中的函数失败时,该函数将使用error
接口返回一个值,以允许调用者处理该失败。在许多情况下,开发人员将使用fmt
包中的fmt.Errorf
函数来返回这些值。不过,在Go 1.13之前,使用此函数的一个缺点是,您将丢失有关可能导致错误返回的任何错误的信息。为了解决这个问题,开发人员要么使用包来提供一种方法将错误“包装”在其他错误中,要么通过在他们的struct
错误类型上实现Error() string
方法来创建自定义错误。然而,如果您有许多不需要由调用者显式处理的错误,有时创建这些struct
类型可能会很繁琐,因此在Go 1.13中,语言添加了一些功能来更容易处理这些情况。
其中一个功能是能够使用带有error
值的fmt.Errorf
函数包装错误,该函数稍后可以展开以访问包装后的错误。这将错误包装功能构建到Go标准库中,因此不再需要使用第三方库。
此外,函数errors.Is
和errors.As
使确定特定错误是否包装在给定错误中的任何位置更容易,还将使您直接访问特定错误,而无需自己拆封所有错误。
在本教程中,你将创建一个程序,使用这些函数在函数返回的错误中包含额外的信息,然后创建你自己的自定义错误struct
,以支持包装和解包装功能。
Go中的返回和处理错误
当程序中发生错误时,最好的做法是处理这些错误,让用户永远不会看到它们——但要处理这些错误,你需要首先了解它们。在Go中,你可以通过使用特殊的interface
类型(即error
接口)从函数中返回有关错误的信息来处理程序中的错误。使用error
接口允许任何Go类型作为error
值返回,只要该类型定义了Error() string
方法。Go标准库提供了为这些返回值创建error
的功能,例如fmt.Errorf
函数。
在本节中,你将创建一个带有函数的程序,该函数使用fmt.Errorf
来返回一个错误,你还将添加一个错误处理程序来检查函数可能返回的错误。如果您想了解有关Go中处理错误的更多信息,请参阅教程,Go中处理错误
许多开发人员都有一个目录来保存当前项目。在本教程中,你将使用一个名为projects
的目录。
首先,创建projects
目录并导航到它:
mkdir projects
cd projects
在projects
目录中,创建一个新的errtutorial
目录来保存新程序:
mkdir errtutorial
接下来,使用cd
命令切换到新目录:
cd errtutorial
进入errtutorial
目录后,使用go mod init
命令创建一个名为==errtutorial==
的新模块:
go mod init errtutorial
创建Go模块后,使用nano
或你喜欢的编辑器在errtutorial
目录中打开一个名为main.go
的文件:
nano main.go
接下来,你将编写一个程序。程序将循环遍历数字1
到3
,并使用名为validateValue
的函数尝试确定这些数字是否有效。如果数字被确定为无效,程序将使用fmt.Errorf
函数生成一个error
值并从函数返回。fmt.Errorf
函数允许你创建一个error
值,其中的错误消息就是你提供给函数的消息。它的工作原理类似于fmt.Printf
,但它不是将消息打印到屏幕上,而是将其作为error
返回。
然后,在main
函数中,将检查错误值是否为nil
。如果是nil
值,则函数成功并打印valid!
消息。如果不是,则打印接收到的错误。
要开始你的程序,将以下代码添加到main.go
文件中:
projects/errtutorial/main.go
package main
import (
"fmt"
)
func validateValue(number int) error {
if number == 1 {
return fmt.Errorf("that's odd")
} else if number == 2 {
return fmt.Errorf("uh oh")
}
return nil
}
func main() {
for num := 1; num <= 3; num++ {
fmt.Printf("validating %d... ", num)
err := validateValue(num)
if err != nil {
fmt.Println("there was an error:", err)
} else {
fmt.Println("valid!")
}
}
}
程序中的validateValue
函数接受一个数字,然后根据它是否被确定为有效值返回一个error
。在这个程序中,数字1
是无效的,并返回错误that's odd
。数字2
无效,并返回错误uh oh
。validateValue
函数使用fmt.Errorf
函数来生成要返回的error
值。fmt.Errorf
函数很方便地返回错误,因为它允许您使用类似于fmt.Printf
或fmt.Sprintf
的格式来格式化错误消息,而不需要将string
传递给errors.New
。
在main
函数中,for
循环将开始迭代从1
到3
的每个数字,并将值存储在num
变量中。在循环体中,调用fmt.Printf
将打印程序当前正在验证的数字。然后,它将调用validateValue
函数并传入当前正在验证的数字num
,并将错误结果存储在err
变量中。最后,如果err
不是nil
,则意味着在验证过程中发生了错误,并使用fmt.Println
打印错误消息。错误检查的else
子句将打印"valid!"
当没有遇到错误时。
保存更改后,使用go run
命令运行程序,并将main.go
作为errtutorial
目录的参数:
go run main.go
运行该程序的输出将表明,对每个数字都运行了验证,数字1
和数字2
都返回了相应的错误:
Outputvalidating 1... there was an error: that's odd
validating 2... there was an error: uh oh
validating 3... valid!
查看程序的输出时,你会发现程序试图验证所有三个数字。第一次它说validateValue
函数返回了that's odd
错误,这是1
的值所期望的。下一个值2
也显示它返回了一个错误,但这次是uh oh
错误。最后,3
返回nil
作为错误值,这意味着没有错误并且数字是有效的。根据validateValue
函数的编写方式,任何非1
或2
的值都会返回nil
错误值。
在本节中,你使用fmt.Errorf
来创建从函数返回的error
值。我们还添加了一个错误处理程序,以便在函数返回任何error
时打印出错误消息。不过,有时候,知道错误的含义可能很有用,而不仅仅是知道错误发生了。在下一节中,你将学习针对特定情况自定义错误处理。
使用哨兵错误处理特定错误
当你从函数中收到一个error
值时,最基本的错误处理是检查error
值是否为nil
。这将告诉你函数是否有错误,但有时你可能希望针对特定的错误情况自定义错误处理。例如,假设你有代码连接到远程服务器,而你得到的唯一错误信息是“you had a error”。你可能想知道这个错误是因为服务器不可用还是连接凭据无效。如果你知道这个错误意味着用户的凭据是错误的,你可能想让用户立即知道。但是,如果错误意味着服务器不可用,您可能需要尝试重新连接几次,然后才能让用户知道。确定这些错误之间的区别可以让你编写更健壮、更友好的程序。
检查特定类型错误的一种方法可能是使用error
类型的error
方法从错误中获取消息,并将该值与你要查找的错误类型进行比较。想象一下,在你的程序中,当错误值为uh oh
时,你想显示一条消息,而不是 there was an error: uh oh
。处理这种情况的一种方法是检查Error
方法的返回值,如下所示:
if err.Error() == "uh oh" {
// Handle 'uh oh' error.
fmt.Println("oh no!")
}
检查err.Error()
的字符串值,看看它是否为uh oh
,就像上面的代码一样,在这种情况下可以工作。但是,如果程序中其他地方的uh oh
错误字符串
稍有不同,代码就无法工作。检查错误如果需要更新错误消息本身,这种方式也可能导致对代码的重大更新,因为每个检查错误的地方都需要更新。以以下代码为例:
func giveMeError() error {
return fmt.Errorf("uh h")
}
err := giveMeError</