问题历史:
Go与许多语言的不同之处在于其错误处理方法,其中错误是值,而不是异常。这种设计旨在实现透明性和简洁性:每当有可能出现问题时,函数明确返回错误,作为第二个返回值。
问题:
许多新手试图将熟悉的try-catch模式应用于Go,结果在大量的错误检查中迷失,或者不使用错误包装来传递上下文。这导致信息丢失和调试困难。
解决方案:
在Go中,可以返回错误的函数声明如下:
func doSomething() (ResultType, error) { // ... if somethingWrong { return nil, errors.New("出现了问题") } return result, nil }
错误检查是调用者的责任:
res, err := doSomething() if err != nil { log.Fatalf("处理失败: %w", err) }
Go 1.13及更高版本允许通过fmt.Errorf("%w", err)“包装”错误,以构建错误链。这改善了诊断,并推动了最佳实践的上下文描述错误。
关键特性:
为什么要创建自己的错误类型,如果可以简单返回errors.New("...")?
正确答案:自定义错误类型不仅可以传达错误文本,还可以保留额外信息以便后续处理(例如返回码、上下文),并通过类型断言实现更细致的处理。
示例:
type NotFoundError struct { Resource string } func (e NotFoundError) Error() string { return fmt.Sprintf("%s 未找到", e.Resource) } // 检查 if _, ok := err.(NotFoundError); ok { // 处理查找错误 }
panic是否是严重错误的良好替代品?
不,Go中的panic仅在真正绝境的情况下使用——例如,程序本身的错误(程序不变量),而不是普通故障(例如,无法打开文件)。将panic用于常规错误的信号是不好的,导致代码难以阅读和管理。
如果在嵌套函数中遗漏错误处理(err)的return,会发生什么?
错误会被“丢失”,代码将继续执行,可能导致错误状态的传播。始终重要的是正确处理每个错误的返回。
_ = ...忽略返回的错误每个函数只是返回errors.New(...),错误没有被包装,错误类型不同,但处理始终是一样的——记录并抛出。因此,日志文件充满了无信息的消息,无法追踪到根本原因。
优点:
缺点:
使用通过fmt.Errorf("%w", err)进行错误包装,自定义错误及有用字段,通过errors.Is()/errors.As()进行检查。因此,可以详细记录,区分业务错误和环境故障,并编写可靠的重试逻辑。
优点:
缺点: