Files
cs-note/hzh/GolangStar/Go语言基础/Go语言error.md
T

4.3 KiB
Raw Blame History

tags, create time
tags create time
go
golang
go基础语法
error
2026-06-07 15:00

Go 语言 error

概述

error 是 Go 的内置接口(只有一个 Error() string 方法)。本文讲解 error 的本质、创建方式、自定义 error、错误包装与比较。

正文

error 到底是什么?

type error interface {
    Error() string
}

error 就是一个接口。任何实现了 Error() string 方法的类型都可以作为 error 返回。这意味着你可以完全自定义错误类型。

[!question] ❓ 思考 既然 error 只是一个接口,那为什么不能直接用 == 比较两个 error?

创建 error 的两种方式

errors.New() — 简单错误

err := errors.New("something went wrong")

底层实现:

func New(text string) error {
    return &errorString{text}  // 返回指针
}

fmt.Errorf() — 带格式化的错误

err := fmt.Errorf("failed to open file %s: %v", path, innerErr)

支持 %w 包装错误(见下文"错误包装"章节)。

[!warning] ⚠️ error 不能用 == 直接比较 errors.New("hello") == errors.New("hello") 结果是 false,因为它们是不同的指针实例。需要比较字符串内容:err.Error() == "hello"。

标准错误处理模式

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

func ReadFile(name string) (string, error) {
    data, err := os.ReadFile(name)
    if err != nil {
        return "", err  // 向上层传递错误
    }
    return string(data), nil
}

// 调用方
content, err := ReadFile("config.json")
if err != nil {
    log.Fatal(err)
}

[!tip] 💡 防御性编程

if err != nil {
    return err  // 尽早返回,避免嵌套
}
// 下面是正常逻辑

这就是著名的 "error laddering"——每个错误处理块都缩进一级,最终正常逻辑在最外层。

自定义 error

实现 Error() string 方法即可:

type NotFoundError struct {
    Resource string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("resource not found: %s", e.Resource)
}

// 使用
err := &NotFoundError{Resource: "user_42"}
fmt.Println(err)  // resource not found: user_42

如果需要携带额外信息(如错误码):

type AppError struct {
    Code int
    Msg  string
}

func (e *AppError) Error() string {
    return fmt.Sprintf("[%d] %s", e.Code, e.Msg)
}

// 提取错误码
func ErrorCode(err error) int {
    if appErr, ok := err.(*AppError); ok {
        return appErr.Code
    }
    return -1
}

[!note] 📝 用类型断言提取信息 err.(*AppError) 将 error 接口还原为具体类型,可以访问额外的字段。

错误包装(Go 1.13+)

fmt.Errorf 支持 %w 来包装错误,保留原始错误链:

func LoadConfig(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        return fmt.Errorf("load config: %w", err)
    }
    return nil
}

// 检查是否包含特定错误
var fsErr *fs.PathError
if errors.As(err, &fsErr) {
    fmt.Printf("文件系统错误: %v\n", fsErr)
}

// 检查错误链中是否有某个错误
if errors.Is(err, io.EOF) {
    fmt.Println("到达文件末尾")
}

[!info] ℹ️ 三个核心函数

函数 用途
errors.Is(err, target) 检查 err 是否在错误链中等于 target
errors.As(err, target) 将 err 链中的第一个匹配类型赋值给 target
fmt.Errorf("%w", err) 包装错误,保留原始错误
graph TD
    A["load config 错误"] --> B["os.Open 错误"]
    B --> C["permission denied"]
    
    style A fill:#f9f,stroke:#333
    style B fill:#bbf,stroke:#333
    style C fill:#ff9,stroke:#333

[!warning] ⚠️ %w vs %v

  • %w — 包装错误,可以用 errors.Is/As 追溯
  • %v — 仅格式化输出,不保留错误链关系

最佳实践总结

场景 推荐做法
简单错误 errors.New()
带上下文的错误 fmt.Errorf("context: %w", err)
需要提取错误码 自定义 struct + Error() string
检查错误类型 errors.As(err, &target)
判断是否某类错误 errors.Is(err, target)
业务逻辑错误 定义专门的错误类型(如 ErrNotFound)