Go: 错误是值 errors are values
错误是值:可以携带信息、传递、检查、比较(控制流)
// 实现了 error 接口类型的值即错误
// go doc builtin.error
type error interface {
Error() string
}Go 的错误处理哲学:错误值须显式处理,把每个错误点变成一个明确的设计决策:这一层是否要处理、解释、包装,还是继续交给上层。
创建错误
- 错误类型(type FooError struct{}):实现
error接口的类型 - 错误值(var ErrBar error):通过
errors.Newfmt.Errorf返回的值;及1中定义的错误类型的值
// 以下两个都是go标准库内置的不可导出的错误类型的值
// go doc -u errors.errorString
errors.New("")
// go doc -u fmt.wrapError
// go doc -u fmt.wrapErrors
fmt.Errorf("%w", err) // NOTE: %w 表示wrap错误,可用errors.Unwrap获取这个错误
// 定义自己的错误类型:...解错误包装
go doc errors.Unwrap实现了Unwrap() error的err都是被封装的错误,可以用errors.Unwrap拿出底层错误;
前面提到fmt.Errorf(%w,err)就是在包装错误
比较错误
Go: 标准库 errors包提供了Is/As/AsType等方法比较判断错误
func Is(err, target error) bool 是判断错误值是否相等;
func As(err error, target any) bool 是判断错误值是否是属于某个类型;
最佳实践
- 不要忽略错误!
- 返回错误的时候,携带(wrap)当前上下文!
- 边界:携带stacktrace,打印日志
局限:非静态类型,无法穷举
go的错误仅仅可以告知可能失败,而无法知道可能以哪些错误失败; go的错误在编译器语义不充分,无法借助编译器进行语义分析
函数签名只有error接口,编译器不知道函数会返回哪些具体错误;
errors.Is/errors.As是运行时检查,不是编译期保证,漏掉某个错误分支编译器不会报错。
对比Rust/Zig的错误集编码在类型系统中,match/switch漏分支编译器直接报错。
但因为错误仅仅是实现了error接口的值,所以其他操作都很灵活:
- 传播:
return err或return fmt.Errorf("...: %w", err),简单但啰嗦(if err != nil样板) - 组合:
%w包装、errors.Join合并多个错误 - 信息:任意struct实现
error接口,想带什么就带什么 - 恢复:
if err != nil { 用默认值 }
Go用编译期穷举能力,换来了错误操作的极致灵活和简单。
评论