浅谈Go的错误处理机制 | 青训营

72 阅读5分钟

Go语言的错误机制是基于返回值的方式,它使用了一个特殊的内置类型 error 来表示错误,并且鼓励开发者以通用的方式处理错误。

1 error 类型

error 是一个内置的接口类型,通常由函数返回来表示执行过程中的错误状态。它只有一个方法,即 Error() 方法,用于返回错误的描述信息。

type error interface {
    Error() string
}

1.1 自定义 error

创建一个自定义错误类型的结构体,通常以该类型的名字作为结构体名。该结构体需要满足 error 接口,即需要实现 Error() string 方法。

// 自定义错误类型
type MyError struct {
    message string
}
​
// 实现 error 接口的 Error 方法
func (e MyError) Error() string {
    return e.message
}

1.2 使用 error

函数可以通过返回 error 来指示它们的执行是否成功。如果函数执行成功,通常返回 nil;如果发生错误,就返回一个非 nil 的错误。

func function() error {
    if someCondition {
        return nil
    } else {
        return MyError{"Something went wrong"}    
    }
}

errors 包是一个标准库中的包,用于创建和处理错误。它提供了一个用于创建简单错误值的函数,以及用于检查错误类型的函数。

  • errors 包提供了一个函数 New,用于创建一个新的错误值。这对于创建简单的错误信息非常有用。

    import "errors"func function() error {
        return errors.New("Something went wrong")
    }
    
  • 可以使用 errors 包中的函数来检查一个错误是否是特定类型的错误。

    errors.Is(err, target) 用于检查一个错误是否与目标错误类型匹配。这个函数返回一个布尔值,表示给定的错误是否是目标错误类型的实例或包装。

    import "errors"
    ​
    err := function()
    if err != nil {
        if errors.Is(err, io.EOF) {
            // 处理 EOF 错误
        }
    }
    
  • Go语言支持错误链,这意味着你可以将一个错误包装在另一个错误中,以保留更多的上下文信息。

    fmt.Errorf 是 Go 语言标准库 fmt 包中提供的一个函数,用于格式化生成一个新的错误。它类似于使用字符串格式化的方式创建错误,并且可以通过 %w 占位符将一个错误包装在另一个错误中,以提供更多的上下文信息。

    if err := function(); err != nil {
        return fmt.Errorf("encountered an error: %w", err)
    }
    
  • 通过使用错误的 Error() 方法,你可以获取错误的描述信息。这对于日志记录和用户友好的错误显示非常有用。

    import "fmt"
    ​
    err := function()
    if err != nil {
        fmt.Println("Error:", err)
    }
    

    将一个实现了 error 接口的对象传递给 fmt.Println 函数或类似的函数时,它们会自动调用对象的 Error() 方法来获取错误的描述信息,并将其打印出来。

2 panicrecover

2.1 defer

defer 是一个关键字,用于延迟(defer)一个函数的执行,直到包含它的函数执行完毕。这意味着无论函数是正常返回还是发生异常退出,被延迟的函数都会在函数退出之前被调用。defer 常用于执行一些清理操作,如关闭文件、释放资源等,以确保在函数结束时这些操作都会被执行。

defer function(arguments)
  • 如果一个函数内有多个 defer,它们会按照倒序的顺序执行,即最后一个 defer 会最先执行,依此类推。
  • defer 延迟的函数在当前函数执行完毕之后才会执行,即使在函数中有 return 语句,也会等待 defer 的函数执行完毕之后再返回。

2.2 panic + recover

panicrecover 是用于处理程序中的异常情况的机制。它们通常用于处理无法恢复的错误和异常。

panic 是一个内置函数,用于表示程序中出现了无法继续执行的严重错误。当程序调用 panic 函数时,当前函数的执行会立即停止,程序会从当前函数的调用栈中退出,然后开始执行每层调用栈的 defer 函数。如果没有遇到 recover,程序将终止并显示一个运行时错误信息。

panic 可以是开发者主动触发的,也可以是某些不可控的错误导致的(如数组越界、空指针解引用等)。

func main() {
    panic("Something went wrong")
}

recover 是一个内置函数,用于在 defer 函数中捕获 panic 导致的程序中断,从而允许程序进行恢复操作。recover 必须在 defer 内部调用,否则它将不会起作用。当 recover 被调用时,它会返回 panic 传递的值,如果没有发生 panic,它将返回 nil

通常,recover 被用于恢复程序的控制流,以便进行一些清理操作或者继续执行一部分逻辑。

func main() {
    // 触发 panic,这里会引发一个中断,但由于下面的 defer 函数,程序不会终止
    panic("Something went wrong")
    
    // 使用 defer 定义一个匿名函数,在 main 函数结束时执行该函数
    defer func() {
        // 在这个匿名函数内部使用 recover 来捕获 panic
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()
}

3 错误处理用例

在Go语言中,编写返回两个值的函数并且其中一个值是 error 类型是非常常见的。这种模式通常用于表示函数的执行结果是否成功,以及在出现错误时提供错误信息。

package main
​
import (
    "errors"
    "fmt"
)
​
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
​
func main() {
    result, err = divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

4 注意事项

  • 不要仅仅使用 if err != nil 进行错误检查。错误可能是其他一些值,需要根据情况进行特定的错误处理。
  • 不要简单地使用 _ 或忽略错误,因为这可能导致潜在的问题被忽略,从而影响程序的健壮性。
  • 除非是无法从中恢复的情况,避免使用 panic。使用 panic 可能会导致程序不可控制地终止,而不是优雅地处理错误。