Go错误处理

错误处理

Posted by 大攀 on Sunday, June 20, 2021

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