golang高性能轻量级错误包装插件库errors的使用
golang高性能轻量级错误包装插件库errors的使用
简介
这是一个为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包装
}
代码说明
- 首先导入标准库的errors包(重命名为stderrors)和第三方errors包
- 定义了几个全局错误变量用于错误包装
- main函数中调用f3()获取错误链
- 使用stderrors.Is()检查错误链中是否包含特定错误
- 打印完整的错误信息,可以看到层层包装的错误链
- f1()创建基础错误
- f2()用ErrGlobal包装f1()的错误
- f3()用ErrGlobal2包装f2()的错误
这个库的主要特点是:
- 高性能,最小内存分配
- 保持错误在函数栈上
- 支持错误链的创建和检查
- 兼容标准库的errors.Is()方法
更多关于golang高性能轻量级错误包装插件库errors的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于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
}
最佳实践
- 只在需要时包装错误 - 不是所有错误都需要堆栈信息
- 避免过度包装 - 多次包装会导致堆栈信息冗余
- 提供有意义的上下文 - 使用 WithMessage 添加有用的上下文信息
- 使用 errors.Is 和 errors.As - 用于错误检查和类型断言
- 考虑性能敏感路径 - 在性能关键路径上避免昂贵的堆栈收集
性能比较
在性能敏感场景下,简单的错误创建可能比带堆栈的错误快 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
提供了良好的平衡。在极端性能敏感的场景,可能需要自定义轻量级实现。