错误处理基础原理
在Go语言中,错误被视作值(values)而非异常(exceptions),这种设计哲学贯穿整个语言体系。error
类型是一个内置接口类型,定义如下:
type error interface {
Error() string
}
标准库中的errors.New()
和fmt.Errorf()
是最基础的错误构造方式:
func validate(input string) error {
if len(input) < 5 {
return errors.New("input too short")
}
return nil
}
错误检查模式采用显式的if err != nil
判断,这种看似冗长的设计带来以下优势:
– 强制开发者立即处理错误
– 错误处理流程显式可见
– 避免异常机制的性能损耗
进阶错误处理模式
错误包装与解包
Go 1.13引入的错误包装机制允许构建错误链:
if err := process(); err != nil {
return fmt.Errorf("processing failed: %w", err)
}
使用errors.Is
和errors.As
进行错误匹配:
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// 处理特定错误
}
var pathError *os.PathError
if errors.As(err, &pathError) {
// 处理PathError类型
}
自定义错误类型
对于需要携带额外上下文的错误,可定义结构体类型:
type APIError struct {
StatusCode int
Message string
Details map[string]interface{}
}
func (e *APIError) Error() string {
return fmt.Sprintf("%d: %s", e.StatusCode, e.Message)
}
并发环境下的错误处理
Goroutine错误传递模式
在并发场景中,推荐使用errgroup
进行错误传播:
import "golang.org/x/sync/errgroup"
func processAll(items []string) error {
var g errgroup.Group
for _, item := range items {
item := item
g.Go(func() error {
return processItem(item)
})
}
return g.Wait()
}
错误聚合模式
当需要收集多个goroutine的错误时:
func batchProcess(items []string) []error {
var (
wg sync.WaitGroup
mu sync.Mutex
errs []error
)
for _, item := range items {
wg.Add(1)
go func(item string) {
defer wg.Done()
if err := process(item); err != nil {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
}
}(item)
}
wg.Wait()
return errs
}
性能关键场景优化
减少错误分配
高频调用场景可使用预定义错误:
var (
ErrInvalidInput = errors.New("invalid input")
ErrSystemBusy = errors.New("system busy")
)
func validate(input string) error {
if input == "" {
return ErrInvalidInput
}
// ...
}
零分配错误模式
对于极端性能场景,可使用哨兵错误:
type validationError string
func (e validationError) Error() string { return string(e) }
const (
ErrEmpty validationError = "empty value"
ErrTooLong validationError = "value too long"
)
行业最佳实践
分层错误处理策略
- 基础设施层:使用原始错误和系统级错误码
- 服务层:包装错误添加上下文
- API边界:转换为标准化的错误响应
日志记录规范
func handleRequest(r *Request) error {
if err := validate(r); err != nil {
log.Printf("validation failed: %+v", err) // 使用%+v打印堆栈
return fmt.Errorf("invalid request: %w", err)
}
// ...
}
测试策略
使用t.Fatal
及时终止错误测试:
func TestProcess(t *testing.T) {
t.Run("invalid input", func(t *testing.T) {
err := process("")
if !errors.Is(err, ErrInvalidInput) {
t.Fatalf("unexpected error: %v", err)
}
})
}
高级模式与陷阱规避
错误恢复策略
在特定场景下合理使用recover
:
func safeCall(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered: %v", r)
}
}()
fn()
return nil
}
常见反模式
- 忽略错误:
_ = operation()
- 过度包装导致信息冗余
- 在循环内部处理错误导致性能问题
- 错误消息缺乏可操作性信息
上下文超时处理
func callWithTimeout(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
done := make(chan error, 1)
go func() { done <- longOperation() }()
select {
case err := <-done:
return err
case <-ctx.Done():
return ctx.Err()
}
}