golang高性能轻量级错误包装插件库errors的使用

golang高性能轻量级错误包装插件库errors的使用

Build Status Go Report Card Documentation Gocover

简介

这是一个为Go语言设计的简单错误处理包,具有最小内存分配和高性能的特点。它经过优化,可以将错误保持在函数栈上。

动机

作者发现现有的错误处理包要么对于简单目的来说过于复杂,要么无法实现简单的目标。

目的与目标

维护一个错误链(某种错误列表),并让系统检查错误的类型是否导致了实际错误。

使用示例

下面是一个完整的示例代码,展示了如何使用这个错误包装库:

package main

import (
    stderrors "errors"

    "github.com/PumpkinSeed/errors"
)

// 定义全局错误变量
var ErrGlobal = errors.New("global err")
var ErrGlobal2 = errors.New("global err 2")
var ErrNotUsed = errors.New("not used err")

func main() {
    err := f3()
    
    // 检查错误链中是否包含特定错误
    stderrors.Is(err, ErrGlobal)  // true
    stderrors.Is(err, ErrGlobal2) // true
    stderrors.Is(err, ErrNotUsed) // false

    // 打印完整的错误信息
    println(err.Error()) // 输出: "global err 2: global err: string1"
}

// 基础错误创建函数
func f1() error {
    return errors.New("string1")
}

// 包装错误的函数
func f2() error {
    return errors.Wrap(f1(), ErrGlobal) // 将f1的错误用ErrGlobal包装
}

// 再次包装错误的函数
func f3() error {
    return errors.Wrap(f2(), ErrGlobal2) // 将f2的错误用ErrGlobal2包装
}

代码说明

  1. 首先导入标准库的errors包(重命名为stderrors)和第三方errors包
  2. 定义了几个全局错误变量用于错误包装
  3. main函数中调用f3()获取错误链
  4. 使用stderrors.Is()检查错误链中是否包含特定错误
  5. 打印完整的错误信息,可以看到层层包装的错误链
  6. f1()创建基础错误
  7. f2()用ErrGlobal包装f1()的错误
  8. f3()用ErrGlobal2包装f2()的错误

这个库的主要特点是:


更多关于golang高性能轻量级错误包装插件库errors的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高性能轻量级错误包装插件库errors的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 高性能轻量级错误包装插件库 errors 使用指南

在 Go 语言中,错误处理是一个核心特性。标准库的 errors 包提供了基本的错误处理功能,但对于需要更丰富的错误上下文和堆栈信息的情况,我们可以使用一些高性能的轻量级错误包装库。

常用错误包装库

1. pkg/errors

pkg/errors 是最流行的错误包装库之一,提供了堆栈跟踪和错误包装功能。

import (
	"fmt"
	"github.com/pkg/errors"
)

func main() {
	err := doSomething()
	if err != nil {
		fmt.Printf("%+v\n", err) // 打印堆栈信息
	}
}

func doSomething() error {
	return errors.New("something went wrong")
}

func doSomethingElse() error {
	return errors.Wrap(doSomething(), "failed to do something else")
}

2. go.uber.org/multierr

当需要合并多个错误时,Uber 的 multierr 是一个好选择。

import (
	"go.uber.org/multierr"
	"errors"
)

func main() {
	var err error
	err = multierr.Append(err, errors.New("first error"))
	err = multierr.Append(err, errors.New("second error"))
	
	// 输出: first error; second error
	fmt.Println(err)
}

3. github.com/hashicorp/go-multierror

HashiCorp 的多错误实现。

import (
	"github.com/hashicorp/go-multierror"
	"errors"
)

func main() {
	var result error
	
	if err := step1(); err != nil {
		result = multierror.Append(result, err)
	}
	if err := step2(); err != nil {
		result = multierror.Append(result, err)
	}
	
	fmt.Println(result)
}

高性能自定义实现

如果需要极高性能的错误处理,可以考虑以下轻量级实现:

1. 带堆栈的基本错误

type stack []uintptr

type withStack struct {
	error
	*stack
}

func Wrap(err error) error {
	if err == nil {
		return nil
	}
	
	return &withStack{
		error: err,
		stack: callers(),
	}
}

func callers() *stack {
	const depth = 32
	var pcs [depth]uintptr
	n := runtime.Callers(3, pcs[:])
	var st stack = pcs[0:n]
	return &st
}

func (w *withStack) Unwrap() error {
	return w.error
}

2. 上下文错误

type withContext struct {
	error
	key   string
	value interface{}
}

func WithContext(err error, key string, value interface{}) error {
	return &withContext{
		error: err,
		key:   key,
		value: value,
	}
}

func (w *withContext) Unwrap() error {
	return w.error
}

func (w *withContext) ContextValue(key string) interface{} {
	if w.key == key {
		return w.value
	}
	if unwrappable, ok := w.error.(interface{ ContextValue(string) interface{} }); ok {
		return unwrappable.ContextValue(key)
	}
	return nil
}

最佳实践

  1. 只在需要时包装错误 - 不是所有错误都需要堆栈信息
  2. 避免过度包装 - 多次包装会导致堆栈信息冗余
  3. 提供有意义的上下文 - 使用 WithMessage 添加有用的上下文信息
  4. 使用 errors.Iserrors.As - 用于错误检查和类型断言
  5. 考虑性能敏感路径 - 在性能关键路径上避免昂贵的堆栈收集

性能比较

在性能敏感场景下,简单的错误创建可能比带堆栈的错误快 10-100 倍。以下是一个基准测试示例:

func BenchmarkSimpleError(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = errors.New("simple error")
	}
}

func BenchmarkStackError(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = Wrap(errors.New("error with stack"))
	}
}

选择错误处理库时,应根据项目需求权衡功能和性能。对于大多数应用,pkg/errors 提供了良好的平衡。在极端性能敏感的场景,可能需要自定义轻量级实现。

回到顶部