TOC
1. 其它语言错误处理
1. 错误码
在过程式语言中通常都是用错误码这种方式来处理错误的,比如C,通过全局的errno变量 + errstr的数组来告诉为什么出错。
为什么这样设计?优缺点是什么?
共用错误 or 妥协
一不小心忘记错误值检查,从而造成代码BUG
演进——引入参数
函数接口变得复杂
依然没有解决成功或失败可以被人忽略的问题
2. try-catch-finally
使用异常的方式来处理错误。
与错误码相比至少进了一大步:
1.函数接口在input和output以及错误处理的语义是比较清楚的;
2.正常逻辑的代码可以与错误处理和资源清理的代码分开,提高了可读性;
3.异常不能被忽略(如果忽略也需要先catch住,这是显示的忽略);
4.在面向对象的语言中,异常是个对象,所以 可以实现多态式的catch;
5.与返回状态码相比,异常捕获 函数可以嵌套调用,或者链式调用;
2. Go 错误处理
1. 基调
- Go函数支持多返回值
- Errors are values
2. 优势
1.函数接口语义明确清晰,参数基本上都是入参,而返回参数把结果和错误分离;
2.错误参数如果要忽略,也需要显式地忽略 _ ;
3.返回的error是个接口 Error() string,所以可以扩展自定义的错误处理;
4.如果一个函数返回了多个不同类型的error,则可以通过断言针对不同类型的错误分别做处理;
3. 有无痛点?
if err != nil ?
- 不妨试试:函数式编程、闭包、结构体封装等方式
4. 错误包装
record not found、invalid connection 怎么快速定位是哪个地方报的错?
我们通常需要包装一下错误,而不是干巴巴地把err给返到上层,我们需要把一些执行上下文加入,初衷是方便快速定位排查问题。
怎么做?
a.
fmt.Errorf("excection something failed: %v", err)
b.
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
c. github.com/pkg/errors
d. Go 1.13之后
// Wrapping errors with %w
if err != nil {
// Return an error which unwraps to err.
return fmt.Errorf("decompress %v: %w", name, err)
}
// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
return u.Unwrap()
}
// Examining errors with Is and As
// The errors.Is function compares an error to a value.
// Similar to:
// if err == ErrNotFound { … }
if errors.Is(err, ErrNotFound) {
// something wasn't found
}
// The As function tests whether an error is a specific type.
// Similar to:
// if e, ok := err.(*QueryError); ok { … }
var e *QueryError
// Note: *QueryError is the type of the error.
if errors.As(err, &e) {
// err is a *QueryError, and e is set to the error's value
}
处理规范
在函数中,通常error是最后一个返回参数,程序通过error变量判定错误类别并处理。
错误不可以使用 ‘_’ 忽略;
原始错误不可以被隐藏,无论是否包装错误,错误文本都将相同;
如发生错误,应避免继续业务逻辑执行(失败重试等特殊场景除外);
对外接口一律返回 error 而不是 panic ;
’父‘协程无法捕获’子‘协程内的Panic,所以每个协程应自行recover();
出错后需要及时清理资源;
错误并不会在第一时间被处理掉,更多的是被包装,再次向上抛出,添加上下文及调用栈信息,方便开发者快速定位问题;
建议在某一统一地方(表现层)进行处理(记录日志、告警机器人等)
建议使用%w包装错误,用Is()或As()判断错误是否属于某一具体错误或某一具体类型;
不要向服务外部公开内部错误细节;
尽量不要在函数返回值直接对变量进行声明;
参考文献:
https://blog.golang.org/error-handling-and-go
https://coolshell.cn/articles/21128.html
https://blog.golang.org/go1.13-errors
https://blog.golang.org/errors-are-values
https://mp.weixin.qq.com/s/KPrzPP797efFUKOTTfY1Ow
github.com/pkg/errors
https://mp.weixin.qq.com/s/zsh__VPbuXkCx4iMOmfceA
https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md
comments powered by Disqus