错误

错误 属于 Go

2 个练习

关于 错误

error 接口

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")
}
Caution

不要假定:当出现错误时,所有函数都会为其他返回值返回零值。 最好的做法是假定:一旦返回了错误,使用其他任何返回值都是不安全的。

举个例子,假设某个函数尝试从文件读取内容,并返回一个只填充了一半的字节切片和一个错误。 如果你在代码里重复使用那个返回的字节切片,并想当然地认为既然出了错它就一定是空的,就可能遇到难以察觉的 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: "...",
  }
}
通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 错误

练习已锁定

再解锁 4 个练习即可练习 错误