Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

errors - 错误处理

概述

errors 包提供了创建和操作错误的基本功能。

errors 包是什么

  • 📦 错误创建:创建新的错误值
  • 🔧 错误包装:包装错误以添加上下文
  • 📋 错误检查:检查错误类型和原因
  • 🛠️ 错误处理:Go 错误处理的核心包

主要用途

  • 🌐 错误创建:创建自定义错误
  • 📧 错误包装:包装错误添加上下文信息
  • 🔐 错误检查:使用 errors.Iserrors.As 检查错误
  • 📊 错误链:处理错误包装链
  • 🖼️ 哨兵错误:定义预声明的错误值
  • 🔑 错误断言:类型断言和错误比较

重要说明

  • ⚠️ 简单错误errors.New 创建简单错误
  • ⚠️ 格式化错误fmt.Errorf 创建带格式的错误
  • ⚠️ 错误包装:Go 1.13+ 支持 %w 包装错误
  • ⚠️ 错误链:包装的错误形成链条
  • 标准库支持:Go 标准库提供完整支持
  • 哨兵错误:推荐使用预声明的错误值
  • 错误检查:使用 errors.Iserrors.As

错误处理示例

// 创建错误
err := errors.New("something went wrong")

// 包装错误
err = fmt.Errorf("failed to process: %w", err)

// 检查错误
if errors.Is(err, ErrNotFound) {
    // 处理特定错误
}

// 提取错误
var target *MyError
if errors.As(err, &target) {
    // 处理自定义错误类型
}

错误基础

错误接口

error 接口定义

type error interface {
    Error() string
}

说明

  • error 是 Go 的内置接口
  • 只有一个方法 Error() 返回错误消息
  • 任何实现了该方法的类型都满足 error 接口

自定义错误示例

type MyError struct {
    Message string
    Code    int
}

func (e *MyError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

错误处理模式

基本模式

result, err := someFunction()
if err != nil {
    // 处理错误
    return err
}
// 使用 result

错误传播

func process() error {
    err := doSomething()
    if err != nil {
        return err  // 传播错误
    }
    return nil
}

核心函数

1. New - 创建错误

func New(text string) error

功能:创建一个新的错误值。

参数

  • text:错误消息文本

返回值

  • error:新的错误值

示例

err := errors.New("invalid input")
fmt.Println(err)  // 输出:invalid input

注意事项

  • ✅ 推荐用于创建简单错误
  • ✅ 适合定义哨兵错误(预声明错误)
  • ❌ 不包含堆栈信息
  • ❌ 不支持格式化

哨兵错误示例

// 在包级别定义
var ErrNotFound = errors.New("not found")

// 使用
if user == nil {
    return ErrNotFound
}

2. Is - 错误匹配

func Is(err, target error) bool

功能:检查 err 是否匹配 target 错误。

参数

  • err:要检查的错误
  • target:目标错误(哨兵错误或特定错误)

返回值

  • bool:如果匹配返回 true

工作原理

  1. 如果 errtarget 都是 nil,返回 false
  2. 如果 err.Error() == target.Error(),返回 true
  3. 如果 err 实现了 Is(error) bool 方法,调用该方法
  4. 如果 err 是包装错误,解包后继续检查

示例

// 定义哨兵错误
var ErrNotFound = errors.New("not found")

// 包装错误
err := fmt.Errorf("user lookup failed: %w", ErrNotFound)

// 检查错误
if errors.Is(err, ErrNotFound) {
    fmt.Println("Resource not found")
}

自定义 Is 方法

type MyError struct {
    Code int
}

func (e *MyError) Error() string {
    return fmt.Sprintf("Error %d", e.Code)
}

func (e *MyError) Is(target error) bool {
    if t, ok := target.(*MyError); ok {
        return e.Code == t.Code
    }
    return false
}

3. As - 错误类型断言

func As(err error, target interface{}) bool

功能:从错误链中提取特定类型的错误。

参数

  • err:要检查的错误
  • target:指向目标错误类型的指针

返回值

  • bool:如果找到匹配类型返回 true

工作原理

  1. 如果 err 是 nil,返回 false
  2. 如果 err 匹配 target 类型,设置 target 并返回 true
  3. 如果 err 是包装错误,解包后继续检查
  4. 如果 err 实现了 As(interface{}) bool 方法,调用该方法

示例

type PathError struct {
    Path string
    Err  error
}

func (e *PathError) Error() string {
    return fmt.Sprintf("path %s: %v", e.Path, e.Err)
}

// 使用
err := &PathError{Path: "/tmp", Err: errors.New("permission denied")}

var pathErr *PathError
if errors.As(err, &pathErr) {
    fmt.Printf("Failed to access path: %s\n", pathErr.Path)
}

注意事项

  • ✅ target 必须是指针类型
  • ✅ 支持从错误链中提取
  • ❌ target 不能是接口类型(error 除外)

4. Unwrap - 解包错误

func Unwrap(err error) error

功能:解包 err,返回内部包装的错误。

参数

  • err:要解包的错误

返回值

  • error:内部错误,如果无法解包返回 nil

工作原理

  1. 如果 err 实现了 Unwrap() error 方法,调用该方法
  2. 否则返回 nil

示例

err1 := errors.New("original error")
err2 := fmt.Errorf("wrapped: %w", err1)

unwrapped := errors.Unwrap(err2)
fmt.Println(unwrapped == err1)  // 输出:true

自定义 Unwrap 方法

type MyError struct {
    Err error
}

func (e *MyError) Error() string {
    return fmt.Sprintf("MyError: %v", e.Err)
}

func (e *MyError) Unwrap() error {
    return e.Err
}

错误包装

fmt.Errorf 包装

基本语法

err := fmt.Errorf("context: %w", originalErr)

注意事项

  • ✅ 使用 %w 包装错误(Go 1.13+)
  • ✅ 可以使用 %v%s 包含错误但不包装
  • %w 只能使用一次
  • ❌ 不能包装 nil 错误

示例

// 包装错误
err := doSomething()
if err != nil {
    return fmt.Errorf("process failed: %w", err)
}

// 多重包装
err = fmt.Errorf("level 1: %w", 
       fmt.Errorf("level 2: %w", 
         errors.New("base error")))

错误链

错误链结构

外层错误 (添加上下文)
  ↓ Unwrap()
中间错误 (添加上下文)
  ↓ Unwrap()
原始错误 (根本原因)

示例

// 创建错误链
baseErr := errors.New("database connection failed")
level1 := fmt.Errorf("query failed: %w", baseErr)
level2 := fmt.Errorf("user lookup failed: %w", level1)

// 检查错误链
errors.Is(level2, baseErr)  // true

// 提取错误
var target error
if errors.As(level2, &target) {
    // 找到第一个匹配的错误
}

完整示例

示例 1:基本错误创建

package main

import (
    "errors"
    "fmt"
)

// 定义哨兵错误
var (
    ErrNotFound      = errors.New("not found")
    ErrInvalidInput  = errors.New("invalid input")
    ErrUnauthorized  = errors.New("unauthorized")
)

// validateInput 验证输入
func validateInput(input string) error {
    if input == "" {
        return ErrInvalidInput
    }
    return nil
}

// findUser 查找用户
func findUser(id int) error {
    if id <= 0 {
        return ErrNotFound
    }
    return nil
}

func main() {
    fmt.Println("=== 基本错误创建 ===\n")
    
    // 1. 使用哨兵错误
    fmt.Println("1. 哨兵错误:")
    
    err := validateInput("")
    if err == ErrInvalidInput {
        fmt.Printf("  ✓ 捕获预期错误:%v\n", err)
    }
    
    err = findUser(-1)
    if err == ErrNotFound {
        fmt.Printf("  ✓ 捕获预期错误:%v\n", err)
    }
    
    // 2. 创建动态错误
    fmt.Println("\n2. 动态错误:")
    
    id := -5
    err = errors.New(fmt.Sprintf("invalid user ID: %d", id))
    fmt.Printf("  错误消息:%v\n", err)
    
    // 3. 错误比较
    fmt.Println("\n3. 错误比较:")
    
    err1 := errors.New("same error")
    err2 := errors.New("same error")
    err3 := err1
    
    fmt.Printf("  err1 == err2: %v (不同实例)\n", err1 == err2)
    fmt.Printf("  err1 == err3: %v (同一实例)\n", err1 == err3)
    fmt.Printf("  errors.Is(err1, err2): %v\n", errors.Is(err1, err2))
    fmt.Printf("  errors.Is(err1, err3): %v\n", errors.Is(err1, err3))
    
    // 4. 哨兵错误的优势
    fmt.Println("\n4. 哨兵错误的优势:")
    fmt.Printf("  ErrNotFound == ErrNotFound: %v\n", ErrNotFound == ErrNotFound)
    fmt.Printf("  errors.Is(ErrNotFound, ErrNotFound): %v\n", 
        errors.Is(ErrNotFound, ErrNotFound))
}

输出

=== 基本错误创建 ===

1. 哨兵错误:
  ✓ 捕获预期错误:invalid input
  ✓ 捕获预期错误:not found

2. 动态错误:
  错误消息:invalid user ID: -5

3. 错误比较:
  err1 == err2: false (不同实例)
  err1 == err3: true (同一实例)
  errors.Is(err1, err2): false
  errors.Is(err1, err3): true

4. 哨兵错误的优势:
  ErrNotFound == ErrNotFound: true
  errors.Is(ErrNotFound, ErrNotFound): true

示例 2:错误包装和展开

package main

import (
    "errors"
    "fmt"
)

// 模拟底层操作
func databaseQuery(id int) error {
    if id < 0 {
        return errors.New("negative ID not allowed")
    }
    if id == 0 {
        return errors.New("ID cannot be zero")
    }
    return nil
}

// 中间层
func getUserFromDB(id int) error {
    err := databaseQuery(id)
    if err != nil {
        return fmt.Errorf("database query failed: %w", err)
    }
    return nil
}

// 顶层
func GetUser(id int) error {
    err := getUserFromDB(id)
    if err != nil {
        return fmt.Errorf("GetUser failed: %w", err)
    }
    return nil
}

func main() {
    fmt.Println("=== 错误包装和展开 ===\n")
    
    // 1. 创建错误链
    fmt.Println("1. 错误链:")
    err := GetUser(-1)
    
    fmt.Printf("  完整错误:%v\n\n", err)
    
    // 2. 解包错误
    fmt.Println("2. 解包错误:")
    
    level1 := err
    level2 := errors.Unwrap(level1)
    level3 := errors.Unwrap(level2)
    
    fmt.Printf("  外层:%v\n", level1)
    fmt.Printf("  中间层:%v\n", level2)
    fmt.Printf("  原始错误:%v\n\n", level3)
    
    // 3. 错误匹配
    fmt.Println("3. 错误匹配:")
    
    baseErr := errors.New("negative ID not allowed")
    
    fmt.Printf("  errors.Is(err, baseErr): %v\n", errors.Is(err, baseErr))
    fmt.Printf("  errors.Is(level1, baseErr): %v\n", errors.Is(level1, baseErr))
    fmt.Printf("  errors.Is(level2, baseErr): %v\n", errors.Is(level2, baseErr))
    fmt.Printf("  errors.Is(level3, baseErr): %v\n\n", errors.Is(level3, baseErr))
    
    // 4. 遍历错误链
    fmt.Println("4. 遍历错误链:")
    
    currentErr := err
    depth := 0
    
    for currentErr != nil {
        fmt.Printf("  深度 %d: %v\n", depth, currentErr)
        currentErr = errors.Unwrap(currentErr)
        depth++
    }
    
    // 5. 包装非错误
    fmt.Println("\n5. 包装非错误(使用 %v):")
    
    originalErr := errors.New("original")
    wrappedWithW := fmt.Errorf("with %%w: %w", originalErr)
    wrappedWithV := fmt.Errorf("with %%v: %v", originalErr)
    
    fmt.Printf("  使用 %%w: %v\n", wrappedWithW)
    fmt.Printf("  使用 %%v: %v\n", wrappedWithV)
    fmt.Printf("  errors.Is(wrappedWithW, original): %v\n", 
        errors.Is(wrappedWithW, originalErr))
    fmt.Printf("  errors.Is(wrappedWithV, original): %v\n", 
        errors.Is(wrappedWithV, originalErr))
}

输出

=== 错误包装和展开 ===

1. 错误链:
  完整错误:GetUser failed: database query failed: negative ID not allowed

2. 解包错误:
  外层:GetUser failed: database query failed: negative ID not allowed
  中间层:database query failed: negative ID not allowed
  原始错误:negative ID not allowed

3. 错误匹配:
  errors.Is(err, baseErr): true
  errors.Is(level1, baseErr): true
  errors.Is(level2, baseErr): true
  errors.Is(level3, baseErr): true

4. 遍历错误链:
  深度 0: GetUser failed: database query failed: negative ID not allowed
  深度 1: database query failed: negative ID not allowed
  深度 2: negative ID not allowed

5. 包装非错误(使用 %v):
  使用 %w: with %w: original
  使用 %v: with %v: original
  errors.Is(wrappedWithW, original): true
  errors.Is(wrappedWithV, original): false

示例 3:错误类型断言

package main

import (
    "errors"
    "fmt"
    "net"
    "os"
)

// CustomError 自定义错误类型
type CustomError struct {
    Code    string
    Message string
}

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

// 模拟可能返回不同类型错误的函数
func operationThatFails(failType string) error {
    switch failType {
    case "custom":
        return &CustomError{Code: "E001", Message: "Custom error occurred"}
    case "net":
        return &net.OpError{Op: "dial", Net: "tcp", Err: errors.New("connection refused")}
    case "path":
        return &os.PathError{Op: "open", Path: "/tmp/test", Err: errors.New("permission denied")}
    default:
        return errors.New("unknown error")
    }
}

func main() {
    fmt.Println("=== 错误类型断言 ===\n")
    
    // 1. 使用 errors.As
    fmt.Println("1. 使用 errors.As:")
    
    err := operationThatFails("custom")
    
    var customErr *CustomError
    if errors.As(err, &customErr) {
        fmt.Printf("  ✓ 提取到 CustomError\n")
        fmt.Printf("    Code: %s\n", customErr.Code)
        fmt.Printf("    Message: %s\n\n", customErr.Message)
    }
    
    // 2. 处理网络错误
    fmt.Println("2. 处理网络错误:")
    
    err = operationThatFails("net")
    
    var netErr *net.OpError
    if errors.As(err, &netErr) {
        fmt.Printf("  ✓ 提取到 OpError\n")
        fmt.Printf("    操作:%s\n", netErr.Op)
        fmt.Printf("    网络:%s\n", netErr.Net)
        fmt.Printf("    错误:%v\n\n", netErr.Err)
    }
    
    // 3. 处理路径错误
    fmt.Println("3. 处理路径错误:")
    
    err = operationThatFails("path")
    
    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        fmt.Printf("  ✓ 提取到 PathError\n")
        fmt.Printf("    操作:%s\n", pathErr.Op)
        fmt.Printf("    路径:%s\n", pathErr.Path)
        fmt.Printf("    错误:%v\n\n", pathErr.Err)
    }
    
    // 4. 包装后的类型断言
    fmt.Println("4. 包装后的类型断言:")
    
    originalErr := &CustomError{Code: "E002", Message: "Original error"}
    wrappedErr := fmt.Errorf("context: %w", originalErr)
    
    var extracted *CustomError
    if errors.As(wrappedErr, &extracted) {
        fmt.Printf("  ✓ 从包装错误中提取\n")
        fmt.Printf("    Code: %s\n", extracted.Code)
        fmt.Printf("    Message: %s\n\n", extracted.Message)
    }
    
    // 5. 类型断言 vs errors.As
    fmt.Println("5. 类型断言 vs errors.As:")
    
    err = operationThatFails("custom")
    
    // 传统类型断言(仅适用于无包装)
    if customErr, ok := err.(*CustomError); ok {
        fmt.Printf("  类型断言成功:%s\n", customErr.Message)
    }
    
    // errors.As(适用于包装错误)
    if errors.As(err, &customErr) {
        fmt.Printf("  errors.As 成功:%s\n", customErr.Message)
    }
}

输出

=== 错误类型断言 ===

1. 使用 errors.As:
  ✓ 提取到 CustomError
    Code: E001
    Message: Custom error occurred

2. 处理网络错误:
  ✓ 提取到 OpError
    操作:dial
    网络:tcp
    错误:connection refused

3. 处理路径错误:
  ✓ 提取到 PathError
    操作:open
    路径:/tmp/test
    错误:permission denied

4. 包装后的类型断言:
  ✓ 从包装错误中提取
    Code: E002
    Message: Original error

5. 类型断言 vs errors.As:
  类型断言成功:Custom error occurred
  errors.As 成功:Custom error occurred

示例 4:自定义错误类型

package main

import (
    "errors"
    "fmt"
)

// APIError API 错误
type APIError struct {
    HTTPStatus int    `json:"status"`
    Code       string `json:"code"`
    Message    string `json:"message"`
    Err        error  `json:"-"`
}

// Error 实现 error 接口
func (e *APIError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("%s: %v", e.Message, e.Err)
    }
    return e.Message
}

// Unwrap 实现 unwrapper 接口
func (e *APIError) Unwrap() error {
    return e.Err
}

// Is 实现错误匹配
func (e *APIError) Is(target error) bool {
    if t, ok := target.(*APIError); ok {
        return e.Code == t.Code
    }
    return false
}

// 预定义的 API 错误
var (
    ErrNotFound     = &APIError{HTTPStatus: 404, Code: "NOT_FOUND", Message: "Resource not found"}
    ErrUnauthorized = &APIError{HTTPStatus: 401, Code: "UNAUTHORIZED", Message: "Authentication required"}
    ErrBadRequest   = &APIError{HTTPStatus: 400, Code: "BAD_REQUEST", Message: "Invalid request"}
)

// NewAPIError 创建新的 API 错误
func NewAPIError(status int, code, message string, err error) *APIError {
    return &APIError{
        HTTPStatus: status,
        Code:       code,
        Message:    message,
        Err:        err,
    }
}

// validateRequest 验证请求
func validateRequest(data map[string]interface{}) error {
    if data == nil {
        return ErrBadRequest
    }
    
    if _, ok := data["id"]; !ok {
        return NewAPIError(400, "MISSING_ID", "Missing required field: id", nil)
    }
    
    return nil
}

// getResource 获取资源
func getResource(id int) error {
    if id <= 0 {
        return ErrNotFound
    }
    
    // 模拟其他错误
    return NewAPIError(500, "INTERNAL_ERROR", "Internal server error", 
        errors.New("database connection failed"))
}

func main() {
    fmt.Println("=== 自定义错误类型 ===\n")
    
    // 1. 使用预定义错误
    fmt.Println("1. 预定义错误:")
    
    err := validateRequest(nil)
    if errors.Is(err, ErrBadRequest) {
        fmt.Printf("  ✓ 捕获预期错误:%v\n", err)
    }
    
    // 2. 创建带上下文的错误
    fmt.Println("\n2. 带上下文的错误:")
    
    err = validateRequest(map[string]interface{}{})
    if apiErr, ok := err.(*APIError); ok {
        fmt.Printf("  HTTP 状态:%d\n", apiErr.HTTPStatus)
        fmt.Printf("  错误码:%s\n", apiErr.Code)
        fmt.Printf("  消息:%s\n", apiErr.Message)
    }
    
    // 3. 包装错误
    fmt.Println("\n3. 包装错误:")
    
    err = getResource(-1)
    wrappedErr := fmt.Errorf("getResource failed: %w", err)
    
    fmt.Printf("  包装后:%v\n", wrappedErr)
    fmt.Printf("  errors.Is(wrappedErr, ErrNotFound): %v\n", 
        errors.Is(wrappedErr, ErrNotFound))
    
    // 4. 提取自定义错误
    fmt.Println("\n4. 提取自定义错误:")
    
    var apiErr *APIError
    if errors.As(wrappedErr, &apiErr) {
        fmt.Printf("  ✓ 提取到 APIError\n")
        fmt.Printf("    HTTP 状态:%d\n", apiErr.HTTPStatus)
        fmt.Printf("    错误码:%s\n", apiErr.Code)
        fmt.Printf("    消息:%s\n", apiErr.Message)
        
        if apiErr.Err != nil {
            fmt.Printf("    原始错误:%v\n", apiErr.Err)
        }
    }
    
    // 5. 错误码匹配
    fmt.Println("\n5. 错误码匹配:")
    
    err1 := &APIError{Code: "NOT_FOUND", Message: "User not found"}
    err2 := &APIError{Code: "NOT_FOUND", Message: "Product not found"}
    err3 := &APIError{Code: "BAD_REQUEST", Message: "Invalid input"}
    
    fmt.Printf("  err1 和 err2 同码:%v\n", errors.Is(err1, err2))
    fmt.Printf("  err1 和 err3 同码:%v\n", errors.Is(err1, err3))
}

输出

=== 自定义错误类型 ===

1. 预定义错误:
  ✓ 捕获预期错误:Invalid request

2. 带上下文的错误:
  HTTP 状态:400
  错误码:MISSING_ID
  消息:Missing required field: id

3. 包装错误:
  包装后:getResource failed: Resource not found
  errors.Is(wrappedErr, ErrNotFound): true

4. 提取自定义错误:
  ✓ 提取到 APIError
    HTTP 状态:404
    错误码:NOT_FOUND
    消息:Resource not found

5. 错误码匹配:
  err1 和 err2 同码:true
  err1 和 err3 同码:false

示例 5:错误处理最佳实践

package main

import (
    "errors"
    "fmt"
    "os"
)

// 定义包级别的哨兵错误
var (
    ErrEmptyFile     = errors.New("file is empty")
    ErrInvalidFormat = errors.New("invalid format")
)

// ConfigError 配置错误
type ConfigError struct {
    Field   string
    Reason  string
    Err     error
}

func (e *ConfigError) Error() string {
    return fmt.Sprintf("config field %q: %s (%v)", e.Field, e.Reason, e.Err)
}

func (e *ConfigError) Unwrap() error {
    return e.Err
}

// ProcessFile 处理文件
func ProcessFile(filename string) error {
    // 检查文件是否存在
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        return fmt.Errorf("file does not exist: %w", err)
    }
    
    // 模拟读取文件
    content := ""
    if content == "" {
        return ErrEmptyFile
    }
    
    return nil
}

// LoadConfig 加载配置
func LoadConfig(data map[string]string) error {
    if data == nil {
        return &ConfigError{
            Field:  "config",
            Reason: "cannot be nil",
            Err:    errors.New("validation failed"),
        }
    }
    
    value, ok := data["port"]
    if !ok {
        return &ConfigError{
            Field:  "port",
            Reason: "is required",
            Err:    ErrInvalidFormat,
        }
    }
    
    if value == "" {
        return &ConfigError{
            Field:  "port",
            Reason: "cannot be empty",
            Err:    ErrInvalidFormat,
        }
    }
    
    return nil
}

func handleError(err error) {
    // 1. 检查哨兵错误
    if errors.Is(err, ErrEmptyFile) {
        fmt.Printf("  处理:文件为空\n")
        return
    }
    
    // 2. 检查标准库错误
    if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
        fmt.Printf("  处理:文件不存在\n")
        return
    }
    
    // 3. 提取自定义错误
    var configErr *ConfigError
    if errors.As(err, &configErr) {
        fmt.Printf("  处理:配置错误 - 字段=%s, 原因=%s\n", 
            configErr.Field, configErr.Reason)
        return
    }
    
    // 4. 默认处理
    fmt.Printf("  处理:未知错误 - %v\n", err)
}

func main() {
    fmt.Println("=== 错误处理最佳实践 ===\n")
    
    // 1. 处理文件不存在
    fmt.Println("1. 文件不存在:")
    err := ProcessFile("nonexistent.txt")
    handleError(err)
    
    // 2. 处理空文件
    fmt.Println("\n2. 空文件:")
    err = ProcessFile("empty.txt")
    handleError(err)
    
    // 3. 处理配置错误 - nil
    fmt.Println("\n3. 配置为 nil:")
    err = LoadConfig(nil)
    handleError(err)
    
    // 4. 处理配置错误 - 缺失字段
    fmt.Println("\n4. 缺失字段:")
    err = LoadConfig(map[string]string{})
    handleError(err)
    
    // 5. 处理配置错误 - 字段为空
    fmt.Println("\n5. 字段为空:")
    err = LoadConfig(map[string]string{"port": ""})
    handleError(err)
    
    // 6. 成功情况
    fmt.Println("\n6. 成功加载:")
    err = LoadConfig(map[string]string{"port": "8080"})
    if err == nil {
        fmt.Printf("  ✓ 配置加载成功\n")
    }
    
    // 7. 错误包装链处理
    fmt.Println("\n7. 错误包装链:")
    
    baseErr := ErrInvalidFormat
    wrapped1 := fmt.Errorf("validation: %w", baseErr)
    wrapped2 := fmt.Errorf("config load: %w", wrapped1)
    
    fmt.Printf("  完整错误链:%v\n", wrapped2)
    
    // 检查原始错误
    if errors.Is(wrapped2, ErrInvalidFormat) {
        fmt.Printf("  ✓ 可以追溯到原始错误\n")
    }
    
    // 提取特定类型
    var configErr *ConfigError
    if errors.As(wrapped2, &configErr) {
        fmt.Printf("  提取到配置错误\n")
    } else {
        fmt.Printf("  未提取到配置错误(预期)\n")
    }
}

输出

=== 错误处理最佳实践 ===

1. 文件不存在:
  处理:文件不存在

2. 空文件:
  处理:文件为空

3. 配置为 nil:
  处理:配置错误 - 字段=config, 原因=cannot be nil

4. 缺失字段:
  处理:配置错误 - 字段=port, 原因=is required

5. 字段为空:
  处理:配置错误 - 字段=port, 原因=cannot be empty

6. 成功加载:
  ✓ 配置加载成功

7. 错误包装链:
  完整错误链:config load: validation: invalid format
  ✓ 可以追溯到原始错误
  未提取到配置错误(预期)

示例 6:标准库错误辅助函数

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    fmt.Println("=== 标准库错误辅助函数 ===\n")
    
    // 1. os.IsNotExist
    fmt.Println("1. os.IsNotExist:")
    
    _, err := os.Open("nonexistent_file.txt")
    if os.IsNotExist(err) {
        fmt.Printf("  ✓ 文件不存在:%v\n", err)
    }
    
    // 2. os.IsExist
    fmt.Println("\n2. os.IsExist:")
    
    err = os.Mkdir("/existing_dir", 0755)
    if os.IsExist(err) {
        fmt.Printf("  ✓ 目录已存在:%v\n", err)
    }
    
    // 3. os.IsPermission
    fmt.Println("\n3. os.IsPermission:")
    
    _, err = os.Open("/root/protected_file")
    if os.IsPermission(err) {
        fmt.Printf("  ✓ 权限不足:%v\n", err)
    }
    
    // 4. 使用 errors.Is 检查标准错误
    fmt.Println("\n4. 使用 errors.Is:")
    
    _, err = os.Open("nonexistent.txt")
    if errors.Is(err, os.ErrNotExist) {
        fmt.Printf("  ✓ errors.Is 检查:文件不存在\n")
    }
    
    // 5. 包装后仍然可以识别
    fmt.Println("\n5. 包装后的错误检查:")
    
    wrappedErr := fmt.Errorf("open file failed: %w", err)
    if errors.Is(wrappedErr, os.ErrNotExist) {
        fmt.Printf("  ✓ 包装后仍然可以识别\n")
    }
    
    // 6. 标准库错误值
    fmt.Println("\n6. 标准库错误值:")
    fmt.Printf("  os.ErrNotExist: %v\n", os.ErrNotExist)
    fmt.Printf("  os.ErrExist: %v\n", os.ErrExist)
    fmt.Printf("  os.ErrPermission: %v\n", os.ErrPermission)
    fmt.Printf("  os.ErrClosed: %v\n", os.ErrClosed)
}

输出

=== 标准库错误辅助函数 ===

1. os.IsNotExist:
  ✓ 文件不存在:open nonexistent_file.txt: no such file or directory

2. os.IsExist:
  ✓ 目录已存在:mkdir /existing_dir: file exists

3. os.IsPermission:
  ✓ 权限不足:open /root/protected_file: permission denied

4. 使用 errors.Is:
  ✓ errors.Is 检查:文件不存在

5. 包装后的错误检查:
  ✓ 包装后仍然可以识别

6. 标准库错误值:
  os.ErrNotExist: file does not exist
  os.ErrExist: file already exists
  os.ErrPermission: permission denied
  os.ErrClosed: file already closed

示例 7:错误处理模式

package main

import (
    "errors"
    "fmt"
)

// 模式 1:哨兵错误
var ErrDivideByZero = errors.New("division by zero")

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, ErrDivideByZero
    }
    return a / b, nil
}

// 模式 2:错误包装
func processDivision(a, b int) error {
    _, err := divide(a, b)
    if err != nil {
        return fmt.Errorf("processDivision(%d, %d) failed: %w", a, b, err)
    }
    return nil
}

// 模式 3:错误检查函数
func checkError(err error) {
    if err == nil {
        return
    }
    
    // 检查特定错误
    if errors.Is(err, ErrDivideByZero) {
        fmt.Printf("  错误:除以零\n")
        return
    }
    
    // 默认处理
    fmt.Printf("  错误:%v\n", err)
}

// 模式 4:错误恢复
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    
    if b == 0 {
        panic("division by zero")
    }
    
    result = a / b
    return result, nil
}

// 模式 5:多错误处理
type MultiError []error

func (m MultiError) Error() string {
    if len(m) == 0 {
        return ""
    }
    return fmt.Sprintf("%v", []error(m))
}

func (m MultiError) HasError() bool {
    return len(m) > 0
}

func validateInputs(inputs ...int) MultiError {
    var errs MultiError
    
    for i, input := range inputs {
        if input < 0 {
            errs = append(errs, fmt.Errorf("input[%d] is negative: %d", i, input))
        }
        if input == 0 {
            errs = append(errs, fmt.Errorf("input[%d] is zero", i))
        }
    }
    
    return errs
}

func main() {
    fmt.Println("=== 错误处理模式 ===\n")
    
    // 1. 哨兵错误模式
    fmt.Println("1. 哨兵错误模式:")
    result, err := divide(10, 0)
    if err != nil {
        fmt.Printf("  结果:%d, 错误:%v\n", result, err)
    }
    
    // 2. 错误包装模式
    fmt.Println("\n2. 错误包装模式:")
    err = processDivision(10, 0)
    checkError(err)
    
    // 3. 错误恢复模式
    fmt.Println("\n3. 错误恢复模式:")
    result, err = safeDivide(10, 0)
    if err != nil {
        fmt.Printf("  恢复的错误:%v\n", err)
    }
    
    // 4. 多错误处理模式
    fmt.Println("\n4. 多错误处理模式:")
    errs := validateInputs(10, -5, 0, -3)
    
    if errs.HasError() {
        fmt.Printf("  验证失败,发现 %d 个错误:\n", len(errs))
        for _, err := range errs {
            fmt.Printf("    - %v\n", err)
        }
    }
    
    // 5. 成功情况
    fmt.Println("\n5. 成功情况:")
    result, err = divide(10, 2)
    if err == nil {
        fmt.Printf("  ✓ 计算成功:%d / %d = %d\n", 10, 2, result)
    }
}

输出

=== 错误处理模式 ===

1. 哨兵错误模式:
  结果:0, 错误:division by zero

2. 错误包装模式:
  错误:processDivision(10, 0) failed: division by zero

3. 错误恢复模式:
  恢复的错误:recovered from panic: division by zero

4. 多错误处理模式:
  验证失败,发现 3 个错误:
    - input[1] is negative: -5
    - input[2] is zero
    - input[3] is negative: -3

5. 成功情况:
  ✓ 计算成功:10 / 2 = 5

最佳实践

✅ 推荐做法

  1. 使用哨兵错误

    // ✅ 推荐
    var ErrNotFound = errors.New("not found")
    
    if err == ErrNotFound {
        // 处理
    }
    
  2. 使用 errors.Is 检查错误

    // ✅ 推荐
    if errors.Is(err, ErrNotFound) {
        // 处理
    }
    
    // ❌ 不推荐(不支持包装)
    if err == ErrNotFound {
        // 处理
    }
    
  3. 使用 errors.As 提取错误

    // ✅ 推荐
    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        // 处理
    }
    
  4. 使用 %w 包装错误

    // ✅ 推荐
    return fmt.Errorf("context: %w", err)
    
    // ❌ 不推荐(不支持错误链)
    return fmt.Errorf("context: %v", err)
    
  5. 定义有意义的错误消息

    // ✅ 推荐
    errors.New("user ID must be positive")
    
    // ❌ 不推荐
    errors.New("error occurred")
    

❌ 不安全做法

  1. 不要忽略错误

    // ❌ 错误
    result, _ := someFunction()
    
    // ✅ 正确
    result, err := someFunction()
    if err != nil {
        return err
    }
    
  2. 不要包装 nil 错误

    // ❌ 错误
    return fmt.Errorf("context: %w", err)  // err 可能是 nil
    
    // ✅ 正确
    if err != nil {
        return fmt.Errorf("context: %w", err)
    }
    return nil
    
  3. 不要过度包装

    // ❌ 错误:过度包装
    return fmt.Errorf("layer3: %w", 
           fmt.Errorf("layer2: %w", 
           fmt.Errorf("layer1: %w", err)))
    
    // ✅ 正确:适度包装
    return fmt.Errorf("operation failed: %w", err)
    

性能优化

1. 避免不必要的错误包装

// ✅ 推荐:直接返回
if err != nil {
    return err
}

// ❌ 不推荐:无意义包装
if err != nil {
    return fmt.Errorf("error: %w", err)
}

2. 使用哨兵错误提高性能

// ✅ 推荐:哨兵错误(指针比较)
if err == ErrNotFound {
    // 快速路径
}

// ❌ 不推荐:字符串比较
if err.Error() == "not found" {
    // 慢
}

总结

核心函数

函数用途返回值
New创建错误error
Is错误匹配bool
As错误断言bool
Unwrap解包错误error

错误包装

方法说明示例
%w包装错误fmt.Errorf("ctx: %w", err)
%v包含错误fmt.Errorf("ctx: %v", err)
Unwrap()解包方法func (e *E) Unwrap() error
Is()匹配方法func (e *E) Is(error) bool
As()断言方法func (e *E) As(interface{}) bool

错误处理模式

模式说明用途
哨兵错误预声明错误值特定错误检查
错误包装添加上下文错误传播
错误链多层包装追溯根本原因
类型断言提取类型获取错误详情
多错误收集多个错误批量验证

标准库辅助函数

函数用途
os.IsNotExist检查文件不存在
os.IsExist检查文件已存在
os.IsPermission检查权限错误
errors.Is通用错误匹配
errors.As通用错误断言

参考资料


最后更新:2026-04-03
Go 版本:Go 1.23+