Skip to content

Go 错误处理

与那些严重依赖异常的语言相比,Go 对错误处理采取了一种独特的方法。Go 函数在可能失败时,通常会返回一个额外的值,类型为 error,与主要结果一起返回,而不是使用 try-catch 块。

error 是一个内置的接口类型,定义如下:

type error interface {
Error() string
}

任何实现了 Error() string 方法的类型都满足 error 接口。nil 的 error 值表示成功,而非 nil 值表示失败。

约定:将 error 作为最后一个返回值

Section titled “约定:将 error 作为最后一个返回值”

根据约定,可能产生错误的函数将其作为最后一个返回值返回。

func DoSomething(input string) (ResultType, error) {
// ... 逻辑 ...
if /* 发生了错误 */ {
return defaultValue, fmt.Errorf("failed to do something with %s: %w", input, specificError)
}
// ... 成功 ...
return successValue, nil // 成功时返回 nil error
}

调用返回 error 的函数的调用者有责任在调用后立即检查 error 值。

result, err := DoSomething("some input")
if err != nil {
// 处理错误(例如,记录日志、返回错误、提供默认值)
log.Printf("Error calling DoSomething: %v", err)
return // 或者返回 err,或者以不同方式处理
}
// 如果 err 是 nil,则继续使用 'result'
fmt.Println("Success! Result:", result)

这种显式的 if err != nil 检查在 Go 代码中无处不在,使错误处理变得可见和强制。

Go 提供了几种创建 error 值的方法:

  1. errors.New(message string): 创建一个带有静态文本消息的简单 error。适用于基本、固定的错误条件。
import "errors"
var ErrNotFound = errors.New("resource not found")
func findResource(id string) error {
if /* ID 为该值的资源不存在 */ {
return ErrNotFound
}
return nil
}
  1. fmt.Errorf(format string, args ...any): 创建一个格式化的错误消息,类似于 fmt.Sprintf。这非常灵活且常用。
import "fmt"
func processFile(filename string) error {
_, err := os.Open(filename)
if err != nil {
// 创建一个包含更多上下文信息的错误消息
return fmt.Errorf("failed to open file '%s': %v", filename, err)
}
// ... 处理文件 ...
return nil
}

现代 Go 鼓励错误包装,以便在不掩盖原始错误的情况下提供上下文。fmt.Errorf 中的 %w 动词用于包装底层 error。

import (
"fmt"
"os"
)
func readFileAndProcess(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
// 包装 os.ReadFile 返回的 error
return fmt.Errorf("error processing file %s: %w", filename, err)
}
// ... 处理数据 ...
return nil
}

errors 包提供了处理被包装 error 的函数:

err := readFileAndProcess("nonexistent.txt")
if err != nil {
fmt.Println("原始错误:", err)
// 检查底层错误是否是特定的 os.ErrNotExist
if errors.Is(err, os.ErrNotExist) {
fmt.Println("检测到文件未找到错误。")
}
// 使用 As 的示例(如果我们有自定义 error 类型)
// var customErr *MyCustomErrorType
// if errors.As(err, &customErr) {
// fmt.Println("Custom error code:", customErr.Code)
// }
}

示例:使用 Sqrt 函数进行错误处理

Section titled “示例:使用 Sqrt 函数进行错误处理”

让我们改进平方根示例,使用 fmt.Errorf 并演示检查错误的模式。

package main
import (
"fmt"
"math"
)
// Sqrt 计算非负数的平方根。
// 如果输入值是负数,则返回一个 error。
func Sqrt(value float64) (float64, error) {
if value < 0 {
// 使用 fmt.Errorf 获取更具信息性的错误消息
return 0, fmt.Errorf("math: cannot calculate square root of negative number %g", value)
}
return math.Sqrt(value), nil // 成功时返回 nil error
}
func main() {
fmt.Println("Attempting Sqrt(-1):")
result1, err1 := Sqrt(-1)
if err1 != nil {
fmt.Println(" Error:", err1)
} else {
fmt.Println(" Result:", result1)
}
fmt.Println("\nAttempting Sqrt(9):")
result2, err2 := Sqrt(9)
if err2 != nil {
fmt.Println(" Error:", err2)
} else {
fmt.Println(" Result:", result2)
}
}

输出:

Attempting Sqrt(-1):
Error: math: cannot calculate square root of negative number -1
Attempting Sqrt(9):
Result: 3

Go 的显式错误处理虽然有时可能比较冗长,但它通过使错误路径清晰并要求开发者面对潜在失败,从而使代码更健壮和易于维护。