哨兵错误,就是定义一些包级别的错误变量,然后在调用的时候外部包可以直接对比变量进行判定,在标准库当中大量的使用了这种方式。例如下方 io
库中定义的错误。
// EOF is the error returned by Read when no more input is available.
// Functions should return EOF only to signal a graceful end of input.
// If the EOF occurs unexpectedly in a structured data stream,
// the appropriate error is either ErrUnexpectedEOF or some other error
// giving more detail.
var EOF = errors.New("EOF")
// ErrUnexpectedEOF means that EOF was encountered in the
// middle of reading a fixed-size block or data structure.
var ErrUnexpectedEOF = errors.New("unexpected EOF")
// ErrNoProgress is returned by some clients of an io.Reader when
// many calls to Read have failed to return any data or error,
// usually the sign of a broken io.Reader implementation.
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
io.EOF
,可以理解为一个标识,代表数据读取完毕。我们在外部判定的时候一般使用等值判定或者使用 errors.Is
进行判断。
if err == io.EOF {
//...
}
if errors.Is(err, io.EOF){
//...
}
Sentinel Error
可以在test中使用,但是Dave
认为这是一种code smell
,应该避免(可能会泛滥)。不建议使用,或者至少不能用于公共API。
这个就类似我们前面定义的 errorString
一样实现了 error
的接口,然后在外部是否类型断言来判断是否是这种错误类型
type MyStruct struct {
s string
name string
path string
}
// 使用的时候
func f() {
switch err.(type) {
case *MyStruct:
// ...
case others:
// ...
}
}
这种方式相对于哨兵来说,可以包含更加丰富的信息,但是同样也将错误的类型暴露给了外部,例如标准库中的 os.PathError
。
不建议使用,或者至少不能用于公共API。
不透明的错误处理,这是最灵活的错误处理策略,因为它要求代码和调用者之间的耦合最少。虽然调用者知道发生了错误,但调用者没有能力看到错误的内部。作为调用者,关于操作的结果,只需指定成功还是失败。这就是不透明错误处理的全部功能–只需返回错误而不假设其内容。
在少数情况下,这种二分错误处理方法是不够的。例如,与进程外的世界进行交互(如网络活动),需要调用方调查错误的性质,以确定重试该操作是否合理。在这种情况下,我们可以断言错误实现了特定的行为,而不是断言错误是特定的类型或值。
type temporary interface {
Temporary() bool
}
func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}
推荐使用opaque error,实在需要细化判断error,也只能判断他的行为,而不是类型和值。判断行为时使用github.com/pkg/errors/errors.go#Cause()
获取原始错误。
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporting target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {
return false
}
}
}
errors.As(a,b)
:通过反射判断a是否和b是一个类型,如果是就把b赋值给a,如果不是,unwrap a,重复前面的步骤。
前一篇文章分析了error标准库里的error.Is(), error.As()
,如果要使用这套逻辑来wrap error,以达到携带更多上下文信息的目的。在做error判断时,需要自行实现Unwrap接口,才能解开已经wrap的error。想想还是挺复杂的,要自定义error,每个自定义error还得去实现Unwrap接口。
github.com/pkg/errors
包即可,而不用再导入同包名的标准库error包(需要做别名处理),不再纠结导入哪个error包。github.com/pkg/errors
使用起来很方便,可以解决上诉问题。关键代码是Wrap(), Cause()
。前者用来wrap error,可携带额外信息和堆栈。后者用来解开已经wrap的error,得到最原始的error。
我们先看一下标准库的源码,我们可以发现当 p.wrappedErr != nil
的时候(也就是有 %w)的时候,会使用一个 wrapError
将错误包装,看 wrapError
的源码可以发现,这个方法只是包装了一下原始错误,并且可以做到附加一些文本信息,但是没有堆栈信息。
func Errorf(format string, a ...interface{}) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
if p.wrappedErr == nil {
err = errors.New(s)
} else {
err = &wrapError{s, p.wrappedErr}
}
p.free()
return err
}
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
Copy
在看一下 pkg/errors 的源码,我肯可以发现除了使用 withMessage
附加了错误信息之外还使用 withStack
附加了堆栈信息,这样我们在程序入口处打印日志信息的时候就可以将堆栈信息一并打出了
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
封装error到对象属性,简化主干代码
对比下面两个函数的处理我们可以发现, count2
使用 sc.Scan
之后一个 if err
的判断都没有,极大的简化了代码,这是因为在 sc.Scan
做了很多处理,像很多类似的,需要循环读取的都可以考虑像这样包装之后进行处理,这样外部包调用的时候就会非常简洁。
// 统计文件行数
func count(r io.Reader) (int, error) {
var (
br = bufio.NewReader(r)
lines int
err error
)
for {
// 读取到换行符就说明是一行
_, err = br.ReadString('\n')
lines++
if err != nil {
break
}
}
// 当错误是 EOF 的时候说明文件读取完毕了
if err != io.EOF {
return 0, err
}
return lines, err
}
func count2(r io.Reader) (int, error) {
var (
sc = bufio.NewScanner(r)
lines int
)
for sc.Scan() {
lines++
}
return lines, sc.Err()
}
sc.Err()
:将error作为字段,链式调用时很有用,gorm就是这么干的。看一个来自 go blog 的例子:https://blog.golang.org/errors-are-values 一般代码
_, err = fd.Write(p0[a:b])
if err != nil {
return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
return err
}
// and so on
errWriter
type errWriter struct {
w io.Writer
err error
}
func (ew *errWriter) write(buf []byte) {
if ew.err == nil {
_, ew.err = ew.w.Write(buf)
}
}
// 使用时
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
return ew.err
}
Post Views: 7