error-handling

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Error Handling

错误处理

Implement idiomatic Go error handling patterns.
实现符合Go语言惯用风格的错误处理模式。

Quick Start

快速入门

Basic error handling:
go
result, err := doSomething()
if err != nil {
    return fmt.Errorf("do something: %w", err)
}
Sentinel error:
go
var ErrNotFound = errors.New("not found")

if errors.Is(err, ErrNotFound) {
    // Handle not found
}
基础错误处理:
go
result, err := doSomething()
if err != nil {
    return fmt.Errorf("do something: %w", err)
}
哨兵错误:
go
var ErrNotFound = errors.New("not found")

if errors.Is(err, ErrNotFound) {
    // Handle not found
}

Instructions

操作步骤

Step 1: Return Errors

步骤1:返回错误

Basic error return:
go
func ReadFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read file %s: %w", path, err)
    }
    return data, nil
}
Multiple return values:
go
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
基础错误返回:
go
func ReadFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read file %s: %w", path, err)
    }
    return data, nil
}
多返回值:
go
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

Step 2: Wrap Errors with Context

步骤2:添加上下文包装错误

Using fmt.Errorf with %w:
go
func ProcessFile(path string) error {
    data, err := ReadFile(path)
    if err != nil {
        return fmt.Errorf("process file: %w", err)
    }
    
    if err := Validate(data); err != nil {
        return fmt.Errorf("validate data: %w", err)
    }
    
    return nil
}
Error chain:
go
// Original error
err := os.Open("file.txt")

// Wrapped once
err = fmt.Errorf("open config: %w", err)

// Wrapped again
err = fmt.Errorf("initialize app: %w", err)

// Unwrap to check original
if errors.Is(err, os.ErrNotExist) {
    // Handle file not found
}
使用带%w的fmt.Errorf:
go
func ProcessFile(path string) error {
    data, err := ReadFile(path)
    if err != nil {
        return fmt.Errorf("process file: %w", err)
    }
    
    if err := Validate(data); err != nil {
        return fmt.Errorf("validate data: %w", err)
    }
    
    return nil
}
错误链:
go
// 原始错误
err := os.Open("file.txt")

// 第一次包装
err = fmt.Errorf("open config: %w", err)

// 再次包装
err = fmt.Errorf("initialize app: %w", err)

// 解包检查原始错误
if errors.Is(err, os.ErrNotExist) {
    // 处理文件不存在情况
}

Step 3: Use Sentinel Errors

步骤3:使用哨兵错误

Define sentinel errors:
go
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

func GetUser(id string) (*User, error) {
    user, ok := cache[id]
    if !ok {
        return nil, ErrNotFound
    }
    return user, nil
}
Check with errors.Is:
go
user, err := GetUser("123")
if errors.Is(err, ErrNotFound) {
    // Handle not found case
    return nil
}
if err != nil {
    // Handle other errors
    return err
}
定义哨兵错误:
go
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

func GetUser(id string) (*User, error) {
    user, ok := cache[id]
    if !ok {
        return nil, ErrNotFound
    }
    return user, nil
}
使用errors.Is检查:
go
user, err := GetUser("123")
if errors.Is(err, ErrNotFound) {
    // 处理资源不存在情况
    return nil
}
if err != nil {
    // 处理其他错误
    return err
}

Step 4: Create Custom Error Types

步骤4:创建自定义错误类型

Error type with fields:
go
type ValidationError struct {
    Field string
    Value interface{}
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Msg)
}

func Validate(user *User) error {
    if user.Email == "" {
        return &ValidationError{
            Field: "email",
            Value: user.Email,
            Msg:   "email is required",
        }
    }
    return nil
}
Check with errors.As:
go
if err := Validate(user); err != nil {
    var valErr *ValidationError
    if errors.As(err, &valErr) {
        fmt.Printf("Field %s failed: %s\n", valErr.Field, valErr.Msg)
    }
    return err
}
带字段的错误类型:
go
type ValidationError struct {
    Field string
    Value interface{}
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Msg)
}

func Validate(user *User) error {
    if user.Email == "" {
        return &ValidationError{
            Field: "email",
            Value: user.Email,
            Msg:   "email is required",
        }
    }
    return nil
}
使用errors.As检查:
go
if err := Validate(user); err != nil {
    var valErr *ValidationError
    if errors.As(err, &valErr) {
        fmt.Printf("Field %s failed: %s\n", valErr.Field, valErr.Msg)
    }
    return err
}

Step 5: Handle Errors Appropriately

步骤5:正确处理错误

Check immediately:
go
// Good
result, err := doSomething()
if err != nil {
    return err
}

// Bad - don't defer error checking
result, err := doSomething()
// ... more code ...
if err != nil {
    return err
}
Don't ignore errors:
go
// Bad
doSomething()

// Good
if err := doSomething(); err != nil {
    log.Printf("warning: %v", err)
}

// Or explicitly ignore
_ = doSomething()
立即检查错误:
go
// 推荐写法
result, err := doSomething()
if err != nil {
    return err
}

// 不推荐 - 不要延迟错误检查
result, err := doSomething()
// ... 更多代码 ...
if err != nil {
    return err
}
不要忽略错误:
go
// 不推荐
doSomething()

// 推荐写法
if err := doSomething(); err != nil {
    log.Printf("warning: %v", err)
}

// 或显式忽略
_ = doSomething()

Common Patterns

常见模式

Error Wrapping Chain

错误包装链

go
func LoadConfig() (*Config, error) {
    data, err := readConfigFile()
    if err != nil {
        return nil, fmt.Errorf("load config: %w", err)
    }
    
    config, err := parseConfig(data)
    if err != nil {
        return nil, fmt.Errorf("load config: %w", err)
    }
    
    return config, nil
}
go
func LoadConfig() (*Config, error) {
    data, err := readConfigFile()
    if err != nil {
        return nil, fmt.Errorf("load config: %w", err)
    }
    
    config, err := parseConfig(data)
    if err != nil {
        return nil, fmt.Errorf("load config: %w", err)
    }
    
    return config, nil
}

Multiple Error Returns

多错误返回

go
func ProcessBatch(items []Item) ([]Result, error) {
    results := make([]Result, 0, len(items))
    
    for _, item := range items {
        result, err := process(item)
        if err != nil {
            return nil, fmt.Errorf("process item %s: %w", item.ID, err)
        }
        results = append(results, result)
    }
    
    return results, nil
}
go
func ProcessBatch(items []Item) ([]Result, error) {
    results := make([]Result, 0, len(items))
    
    for _, item := range items {
        result, err := process(item)
        if err != nil {
            return nil, fmt.Errorf("process item %s: %w", item.ID, err)
        }
        results = append(results, result)
    }
    
    return results, nil
}

Collecting Multiple Errors

收集多个错误

go
type MultiError []error

func (m MultiError) Error() string {
    var msgs []string
    for _, err := range m {
        msgs = append(msgs, err.Error())
    }
    return strings.Join(msgs, "; ")
}

func ValidateAll(users []*User) error {
    var errs MultiError
    
    for _, user := range users {
        if err := Validate(user); err != nil {
            errs = append(errs, err)
        }
    }
    
    if len(errs) > 0 {
        return errs
    }
    return nil
}
go
type MultiError []error

func (m MultiError) Error() string {
    var msgs []string
    for _, err := range m {
        msgs = append(msgs, err.Error())
    }
    return strings.Join(msgs, "; ")
}

func ValidateAll(users []*User) error {
    var errs MultiError
    
    for _, user := range users {
        if err := Validate(user); err != nil {
            errs = append(errs, err)
        }
    }
    
    if len(errs) > 0 {
        return errs
    }
    return nil
}

Panic and Recover

Panic与Recover

Use panic for programmer errors:
go
func MustCompile(pattern string) *regexp.Regexp {
    re, err := regexp.Compile(pattern)
    if err != nil {
        panic(err)  // Invalid pattern is programmer error
    }
    return re
}
Recover from panics:
go
func SafeExecute(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    
    fn()
    return nil
}
Panic用于程序员错误:
go
func MustCompile(pattern string) *regexp.Regexp {
    re, err := regexp.Compile(pattern)
    if err != nil {
        panic(err)  // 无效正则属于程序员错误
    }
    return re
}
从Panic中恢复:
go
func SafeExecute(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    
    fn()
    return nil
}

Error Context

错误上下文

go
type contextError struct {
    op  string
    err error
}

func (e *contextError) Error() string {
    return fmt.Sprintf("%s: %v", e.op, e.err)
}

func (e *contextError) Unwrap() error {
    return e.err
}

func withContext(op string, err error) error {
    if err == nil {
        return nil
    }
    return &contextError{op: op, err: err}
}
go
type contextError struct {
    op  string
    err error
}

func (e *contextError) Error() string {
    return fmt.Sprintf("%s: %v", e.op, e.err)
}

func (e *contextError) Unwrap() error {
    return e.err
}

func withContext(op string, err error) error {
    if err == nil {
        return nil
    }
    return &contextError{op: op, err: err}
}

Error Handling in HTTP

HTTP场景下的错误处理

go
func handler(w http.ResponseWriter, r *http.Request) {
    user, err := getUser(r.Context(), r.URL.Query().Get("id"))
    if errors.Is(err, ErrNotFound) {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    if errors.Is(err, ErrUnauthorized) {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    if err != nil {
        log.Printf("get user: %v", err)
        http.Error(w, "Internal error", http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(user)
}
go
func handler(w http.ResponseWriter, r *http.Request) {
    user, err := getUser(r.Context(), r.URL.Query().Get("id"))
    if errors.Is(err, ErrNotFound) {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    if errors.Is(err, ErrUnauthorized) {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    if err != nil {
        log.Printf("get user: %v", err)
        http.Error(w, "Internal error", http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(user)
}

Error Handling in Goroutines

Goroutine中的错误处理

go
func processAsync(items []Item) error {
    errCh := make(chan error, len(items))
    
    for _, item := range items {
        go func(it Item) {
            errCh <- process(it)
        }(item)
    }
    
    // Collect errors
    for range items {
        if err := <-errCh; err != nil {
            return fmt.Errorf("process failed: %w", err)
        }
    }
    
    return nil
}
go
func processAsync(items []Item) error {
    errCh := make(chan error, len(items))
    
    for _, item := range items {
        go func(it Item) {
            errCh <- process(it)
        }(item)
    }
    
    // 收集错误
    for range items {
        if err := <-errCh; err != nil {
            return fmt.Errorf("process failed: %w", err)
        }
    }
    
    return nil
}

Best Practices

最佳实践

  1. Return errors, don't panic: Except for programmer errors
  2. Check errors immediately: Don't defer checking
  3. Wrap errors with context: Use fmt.Errorf with %w
  4. Use sentinel errors: For expected error cases
  5. Use custom types: For errors needing additional data
  6. errors.Is for sentinel: Check wrapped sentinel errors
  7. errors.As for types: Extract custom error types
  8. Don't ignore errors: Handle or explicitly ignore with _
  9. Error messages lowercase: Start with lowercase, no punctuation
  10. Add context: Include operation and relevant data
  1. 返回错误,而非Panic:除非是程序员错误
  2. 立即检查错误:不要延迟检查
  3. 添加上下文包装错误:使用带%w的fmt.Errorf
  4. 使用哨兵错误:针对预期的错误场景
  5. 使用自定义类型:针对需要附加数据的错误
  6. 用errors.Is检查哨兵错误:检查被包装的哨兵错误
  7. 用errors.As提取错误类型:提取自定义错误类型
  8. 不要忽略错误:处理或用_显式忽略
  9. 错误消息小写开头:以小写字母开头,无标点符号
  10. 添加上下文信息:包含操作名称和相关数据

Anti-Patterns

反模式

Don't:
  • Ignore errors silently
  • Use panic for normal errors
  • Return error strings (use error types)
  • Check error strings (use errors.Is/As)
  • Create errors with fmt.Sprintf (use fmt.Errorf)
  • Wrap errors without %w (loses error chain)
请勿:
  • 静默忽略错误
  • 针对普通错误使用Panic
  • 返回错误字符串(使用错误类型)
  • 检查错误字符串(使用errors.Is/As)
  • 用fmt.Sprintf创建错误(使用fmt.Errorf)
  • 不使用%w包装错误(会丢失错误链)

Troubleshooting

问题排查

Error not matching with errors.Is:
  • Ensure using %w when wrapping
  • Check sentinel error is same instance
Can't extract error type:
  • Use errors.As, not type assertion
  • Ensure error type is pointer
Lost error context:
  • Wrap errors at each level
  • Include operation name in wrap
使用errors.Is无法匹配错误:
  • 确保包装时使用了%w
  • 检查哨兵错误是否为同一实例
无法提取错误类型:
  • 使用errors.As,而非类型断言
  • 确保错误类型是指针类型
丢失错误上下文:
  • 在每个层级包装错误
  • 包装时包含操作名称