四时宝库

程序员的知识宝库

Go语言错误处理大全_go 语言 gui

Go错误处理哲学

Go语言采用显式错误返回机制,这与传统的异常处理有本质区别:

  1. 错误即值(Errors are values):错误被视为普通的值类型
  2. 显式处理:要求开发者必须主动检查和处理错误
  3. 无异常机制:不提供try-catch-finally结构

基础错误处理模式

1. 标准错误处理

func ReadFile(filename string) ([]byte, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("open %s: %w", filename, err)
	}
	defer f.Close()

	data, err := io.ReadAll(f)
	if err != nil {
		return nil, fmt.Errorf("read %s: %w", filename, err)
	}

	return data, nil
}

2. 错误检查工具函数

func check(err error) {
	if err != nil {
		log.Fatalf("fatal error: %v", err)
	}
}

// 使用示例
func main() {
	f, err := os.Open("config.json")
	check(err)
	defer f.Close()
	// ...
}

官方推荐实践

1. 错误包装(Error Wrapping)

Go 1.13+ 引入了错误包装机制:

if err != nil {
    return fmt.Errorf("processing config: %w", err)
}

解包错误:

if errors.Is(err, os.ErrNotExist) {
    // 处理文件不存在的特定错误
}

var pathError *os.PathError
if errors.As(err, &pathError) {
    // 处理PathError类型错误
}

2. 错误定义最佳实践

自定义错误类型:

type ConfigError struct {
	ConfigFile string
	Err        error
}

func (e *ConfigError) Error() string {
	return fmt.Sprintf("config file %s: %v", e.ConfigFile, e.Err)
}

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

// 使用示例
func loadConfig() error {
	// ...
	return &ConfigError{
		ConfigFile: "app.conf",
		Err:        os.ErrNotExist,
	}
}

哨兵错误(Sentinel Errors):

var (
    ErrInvalidInput = errors.New("invalid input")
    ErrNotFound     = errors.New("not found")
)

func Validate(input string) error {
    if input == "" {
        return ErrInvalidInput
    }
    // ...
}

高级错误处理模式

1. 错误分类处理

func HandleError(err error) {
	switch {
	case errors.Is(err, ErrInvalidInput):
		log.Println("warning:", err)
	case errors.Is(err, ErrNotFound):
		log.Println("not found:", err)
		createDefaultConfig()
	default:
		log.Println("critical error:", err)
		os.Exit(1)
	}
}

2. 错误上下文增强

func processRequest(req *Request) error {
	if err := validateRequest(req); err != nil {
		return fmt.Errorf("request %d validation failed: %w", req.ID, err)
	}
	// ...
}

3. 错误重试机制

func Retry(attempts int, fn func() error) error {
	var err error
	for i := 0; i < attempts; i++ {
		if err = fn(); err == nil {
			return nil
		}
		time.Sleep(time.Second * time.Duration(i+1))
	}
	return fmt.Errorf("after %d attempts: %w", attempts, err)
}

工程实践建议

1. 错误日志记录

func logError(err error, context ...interface{}) {
	entry := log.WithFields(log.Fields{
		"error":   err.Error(),
		"context": context,
		"stack":   string(debug.Stack()),
	})
	entry.Error("operation failed")
}

借助开源日志库可以快速定制化和记录错误日志。

2. HTTP错误处理

func writeError(w http.ResponseWriter, err error) {
	var status int
	switch {
	case errors.Is(err, ErrNotFound):
		status = http.StatusNotFound
	case errors.Is(err, ErrInvalidInput):
		status = http.StatusBadRequest
	default:
		status = http.StatusInternalServerError
	}

	w.WriteHeader(status)
	json.NewEncoder(w).Encode(map[string]interface{}{
		"error": err.Error(),
	})
}

3. 数据库错误处理

func (r *UserRepo) GetByID(id int) (*User, error) {
	user := &User{}
	err := r.db.Where("id = ?", id).First(user).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrUserNotFound
		}
		return nil, fmt.Errorf("database error: %w", err)
	}
	return user, nil
}

错误处理的“坏”例子

  • 忽略错误
f, _ := os.Open("file.txt") // 没有处理打开资源时的返回错误
  • 过度泛化的错误
// 不够具体
if err != nil {
    return errors.New("something went wrong")
}
  • 错误吞没
// 丢失原始错误信息
if err != nil {
    log.Println("error occurred")
    return nil
}
  • 过早终止
// 可能不需要直接退出
if err != nil {
    log.Fatal(err)
}

错误处理工具库推荐

  • github.com/uber-go/multierr
package main

import (
	"errors"
	"fmt"

	"go.uber.org/multierr"
)

func main() {
	ExampleErrors()
}

func ExampleErrors() {
	err := multierr.Combine(
		nil, // successful request
		errors.New("call 2 failed"),
		errors.New("call 3 failed"),
	)
	err = multierr.Append(err, nil) // successful request
	err = multierr.Append(err, errors.New("call 5 failed"))

	errors := multierr.Errors(err)
	for _, err := range errors {
		fmt.Println(err)
	}
	// Output:
	// call 2 failed
	// call 3 failed
	// call 5 failed
}

总结:优雅错误处理原则

  1. 透明性:错误应包含足够上下文信息
  2. 可追溯性:保留错误链方便调试
  3. 一致性:团队采用统一的错误处理风格
  4. 适当抽象:在适当层级处理错误
  5. 文档化:公开API的错误情况应有文档说明

"好的错误处理不是事后添加的功能,而是从一开始就设计的系统特性。" - Go Proverbs

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接