文章目录

Go 错误处理:error 接口与 if err != nil

发布于 2026-04-02 16:17:05 · 浏览 11 次 · 评论 0 条

Go 错误处理:error 接口与 if err != nil

Go 语言没有异常机制,而是通过内置的 error 类型来显式处理错误。理解 error 接口和 if err != nil 模式是写出健壮 Go 程序的基础。


1. 认识 error 接口

定义一个变量为 error 类型,本质上就是实现了如下接口:

type error interface {
    Error() string
}

这意味着:只要一个类型有一个返回字符串的 Error() 方法,它就自动满足 error 接口

创建自定义错误最简单的方式是使用标准库中的 errors.New

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("文件未找到")
    fmt.Println(err.Error()) // 输出:文件未找到
}

你也可以用 fmt.Errorf 动态生成带格式的错误信息:

err := fmt.Errorf("无法打开 %s: 权限不足", filename)

2. 函数如何返回错误

在 Go 中,函数通常将 error 作为最后一个返回值。调用者必须显式检查该值。

编写一个可能失败的函数:

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

调用时必须处理错误:

result, err := divide(10, 0)
if err != nil {
    fmt.Println("出错了:", err)
    return
}
fmt.Println("结果:", result)

关键规则:当操作成功时,error 返回 nil;失败时返回非 nilerror 实例。


3. 正确使用 if err != nil

这是 Go 中最核心的错误处理模式。每次调用可能返回错误的函数后,立即检查 err 是否为 nil

基本模式

value, err := someFunction()
if err != nil {
    // 处理错误:记录、返回、退出等
    log.Printf("操作失败: %v", err)
    return err // 或 os.Exit(1),或默认值
}
// 继续使用 value

嵌套过深?提前返回!

避免“金字塔式”嵌套。一旦发现错误,立即返回,保持代码扁平:

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return fmt.Errorf("打开文件失败: %w", err)
    }
    defer file.Close()

    data, err := io.ReadAll(file)
    if err != nil {
        return fmt.Errorf("读取文件失败: %w", err)
    }

    // 处理 data...
    return nil
}

注意这里使用了 %w 动词(Go 1.13+),它允许包装错误以便后续解包。


4. 错误比较与类型断言

有时你需要区分不同类型的错误以采取不同措施。

使用 errors.Is 判断特定错误

标准库中一些包定义了“哨兵错误”(sentinel errors),如 os.ErrNotExist

不要直接用 == 比较(因为包装后可能不相等),而应使用 errors.Is

_, err := os.Open("missing.txt")
if errors.Is(err, os.ErrNotExist) {
    fmt.Println("文件确实不存在")
} else if err != nil {
    fmt.Println("其他错误:", err)
}

使用 errors.As 提取错误类型

如果错误是一个结构体,你想访问其字段,使用 errors.As

var pathError *os.PathError
if errors.As(err, &pathError) {
    fmt.Printf("路径错误: %s (操作: %s)", pathError.Path, pathError.Op)
}

这会沿着错误链查找是否包含 *os.PathError 类型。


5. 自定义错误类型

当需要携带额外信息(如错误码、上下文)时,定义自己的错误结构体

创建一个带状态码的错误:

type APIError struct {
    Code    int
    Message string
}

func (e *APIError) Error() string {
    return fmt.Sprintf("API错误 [%d]: %s", e.Code, e.Message)
}

返回它:

func callAPI() error {
    return &APIError{Code: 404, Message: "资源未找到"}
}

使用 errors.As 获取原始类型:

err := callAPI()
var apiErr *APIError
if errors.As(err, &apiErr) {
    if apiErr.Code == 404 {
        // 重试或跳过
    }
}

6. panic 不是错误处理

避免在常规错误处理中使用 panicpanic 应仅用于程序无法继续运行的致命错误(如配置解析失败、不可恢复的状态损坏)。

正确做法:始终通过 error 返回值传递可预期的失败

例如,不要这样写:

// ❌ 错误示范
func badDivide(a, b float64) float64 {
    if b == 0 {
        panic("除零错误") // 不要这样做!
    }
    return a / b
}

而应返回 error,让调用者决定如何应对。


7. 最佳实践总结

场景 推荐做法
普通错误 立即检查 if err != nil,处理或返回
需要附加信息 使用 fmt.Errorf + %w 包装错误
区分错误类型 使用 errors.Iserrors.As
自定义错误 实现 Error() string 方法
资源清理 在检查错误前使用 defer

记住:Go 的错误处理哲学是“显式优于隐式”。虽然写起来略显啰嗦,但它迫使开发者直面失败路径,从而构建更可靠的系统。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文