Go 中的错误处理不通过异常来完成。
相反,错误是普通的_值_,其类型实现了内置的 error 接口。
error 接口非常精简。
它只包含一个方法 Error(),该方法以字符串的形式返回错误消息。
type error interface {
Error() string
}
每当你定义一个函数,而该函数在执行过程中可能出现需要传递给调用方的错误时,你就需要把 error 作为返回类型之一。
如果函数有多个返回值,按照惯例,error 总是最后一个。
func DoSomething() (int, error) {
// ...
}
你不必总是自己实现 error 接口。
要创建一个简单的错误,可以使用标准库包 errors 中的 errors.New() 函数。
你只需要传入以字符串形式表示的错误消息,errors.New() 会负责创建一个包含你的消息并实现了 error 接口的值。
如果函数返回一个错误,良好的做法是让其他所有返回值都返回零值:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
不要假定:当出现错误时,所有函数都会为其他返回值返回零值。 最好的做法是假定:一旦返回了错误,使用其他任何返回值都是不安全的。
举个例子,假设某个函数尝试从文件读取内容,并返回一个只填充了一半的字节切片和一个错误。 如果你在代码里重复使用那个返回的字节切片,并想当然地认为既然出了错它就一定是空的,就可能遇到难以察觉的 bug。
唯一的例外是函数文档明确说明:在出错的情况下,其他返回值仍有意义。
示例参见[缓冲区中 Write 的文档][buffer-write]。
请确保你提供的错误消息尽可能具体,因为默认情况下错误不包含任何堆栈跟踪。 按照惯例,错误消息应以小写字母开头,且结尾不加句号。
如果你想在多个地方使用这样的错误(或者想让包的使用者能够拿到该错误),就应该为错误声明一个变量,而不是内联使用 errors.New。
按照惯例,变量名应以 Err 或 err 开头(取决于它是否被导出)。
这些错误变量通常被称为_哨兵错误_。
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
用 nil 作为错误返回值,表示函数执行期间没有发生错误:
func Foo() (int, error) {
return 10, nil
}
如果你需要用一些变量构造更复杂的错误消息,可以使用字符串格式化。
Go 有一个专门的 fmt.Errorf 函数用于此目的。
它会构造错误消息字符串并返回一个错误。
input := 123
action := "UPDATE"
err := fmt.Errorf("invalid input %d for action %s", input, action)
err.Error()
// => "invalid input 123 for action UPDATE"
如果你调用一个会返回错误的函数,通常会把错误值存放在一个名为 err 的变量中。
在使用函数的实际结果之前,你需要检查没有发生错误。
我们可以用 == 和 != 把错误与 nil 进行比较,当 err 不是 nil 时,就说明发生了错误。
为了避免代码的“happy path”出现嵌套,应先处理错误情况,通常通过提前返回来处理。 更多细节,请参阅 Mat Ryer 的这篇著名文章。
// Do this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
// Not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
// handle err
} else {
// do something with file
}
}
// Also not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err == nil {
// do something with file
}
// handle err
}
大多数情况下,错误会像上面第一个示例那样沿函数调用栈向上返回。 另一种处理方式是把错误记录下来,然后继续执行别的操作。 良好的做法是:要么返回错误,要么记录错误,绝不要两者都做。
由于 Go 中大多数函数都把错误作为返回值之一,你会在 Go 代码里到处看到、用到 if err != nil 这种模式。
如果你希望错误包含的信息不只是错误消息字符串,可以创建自定义错误类型。
如前所述,任何实现了 error 接口(也就是有一个 Error() string 方法)的东西,在 Go 中都可以当作错误使用。
通常用结构体来创建自定义错误类型。
按照惯例,自定义错误类型的名称应以 Error 结尾。
另外,最好用指针接收者来实现 Error() string 方法,原因参见这条 Stack Overflow 评论。
注意,这意味着你需要返回指向自定义错误的指针,否则它不会被算作 error,因为非指针值没有提供 Error() string 方法。
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}