在什么情况下使用panic?
panic
退出panic
退出(预防编程)panic
,应该返回error
gin
中间件需要使用recover
预防panic
程序退出goroutine
worker
消息通知的方式进行处理,避免请求量大时大量的goroutine
创建goroutine
时,应该使用统一的Go
函数创建,这个函数中会进行recover
,避免因为野生goroutine
panic 导致主程序退出func Go(f func()){go func(){ // defer recover 捕获panic defer func(){ if err := recover(); err != nil { log.Printf("panic: %+v", err) } }() f()}()}
在什么情况下使用error
?
github.com/pkg/errors
处理相应的错误, 注意在公共库中,一般不使用这个error
应该是函数的最后一个返回值,当 error
不为 nil
时,函数的其他返回值是不可用的状态,不应该对其他返回值做任何期待
func f()(io.Reader, *S1,error)
这里,如果error 不为nil,我们不知道io.Reader
中是否有返回值,可能有,也可能没有,也有可能只有一部分if err != nil
出现错误时及时返回,使代码是一条流畅的直线, 避免过多的嵌套
// good casefunc f() error { a, err := A() if err != nil { return err } // ... 其他逻辑 return nil}
// bad casefunc f() error { a, err := A() if err == nil { // 其他逻辑 } return err}errors.New
或者errors,Errorf
返回错误
func (u *usecese) usecase1() error { money := u.repo.getMoney(uid) if money < 10 { return errors.Errorf("用户余额不足, uid: %d, money: %d", uid, money) } // 其他逻辑 return nil}errors.Wrap
添加堆栈信息
func f() error { err := json.Unmashal(&a, data) if err != nil { return errors.Wrap(err, "其他附加信息") } // 其他逻辑 return nil}
errors.Wrap
只需要在错误第一次出现时进行 errors.Wrap 即可%+v
进行统一打印,例如 http/rpc 服务的中间件errors.Is
进行比较
func f() error { err := A() if errors.Is(err, io.EOF){ return nil } // 其他逻辑 return nil}errors.As
进行赋值
func f() error(){ err := A() var errA errorA if erros.As(err, &errA){ //... } // 其他逻辑 return nil}panic
翻看标准库的源代码我们可以发现, errors 库中的 errorString 结构体实现了 error 接口,为什么在 New 一个 error 的时候会返回一个结构体的指针呢?
// New returns an error that formats as the given text.// Each call to New returns a distinct error value even if the text is identical.func New(text string) error { return &errorString{text}} // errorString is a trivial implementation of error.type errorString struct { s string} func (e *errorString) Error() string { return e.s}
先来看一个例子,我们同样创建了 errorString
的结构体,自定义的和标准库中的唯一不同就是,自建的这个返回的是值,而不是指针。
在 main 函数的对比中我们就可以发现,我们自定义的 errorString 在对比的时候只要对应的字符串相同就会返回 true,但是标准库的包不会。
这是因为,在对比两个 struct 是否相同的时候,会去对比,这两个 struct 里面的各个字段是否是相同的,如果相同就返回true,但是对比指针的时候会去判断两个指针的地址是否一致。
如果字符串相等就返回 true 会导致什么问题呢?
如果我有两个包,定义了两个错误,他们其实是两个相同的字符串,在其他库调用对比的时候,可能会由于不同的书写顺序,走进不同的分支导致一些奇奇怪怪的错误
// 自定义错误类型 import "erros" type errorString struct { text string} func (e errorString) Error() string { return e.text} // New 创建一个自定义错误,返回的时结构体,而不是指针func New(s string) error { return errorString{text: s}} // 使用自定义错误实例化错误,返回的时 errorString结构体var errorString1 = New("test a") // 使用标准库实例化一个错误,是一个指针var err1 = errors.New("test b") func main() { if errorString1 == New("test a") { fmt.Println("err string a") // 会输出 } // 两次实例化的指针,地址并不一样 if err1 == errors.New("test b") { fmt.Println("err b") // 不会输出 }}
哨兵错误,就是定义一些包级别的错误变量,然后在调用的时候外部包可以直接对比变量进行判定,在标准库当中大量的使用了这种方式
例如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")
在外部判定的时候一般使用等值判定或者使用 errors.Is
进行判断
if err == io.EOF { //...} if errors.Is(err, io.EOF){ //...}
这种错误处理方式有一个问题是,将 error 当做包的 API 暴露给了第三方,这样会导致在做重构或者升级的时候很麻烦,并且这种方式包含的错误信息会十分的有限
通过类型断言的方式判断错误类型
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()}
在 go 中常常会存在大量的 if err 代码,下面介绍两种常见的减少这种代码的方式
对比下面两个函数的处理我们可以发现, 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} // goodfunc count2(r io.Reader) (int, error) { var ( sc = bufio.NewScanner(r) lines int ) for sc.Scan() { lines++ } return lines, sc.Err()}
一般代码
_, 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 { return } _, 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 onif ew.err != nil { return ew.err}
这种用法,将重复的逻辑进行了封装,然后把 error 暂存,然后我们就只需要在最后判断一下 error 就行了
errors.wrap
有何作用,为什么不用标准库的 fmt.Errorf("%w")?
标准库的源码,我们可以发现当 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}
因为每一次 errors.Wrap
的调用都会为错误添加堆栈信息,如果处处调用那会有大量的无用堆栈,先看一下只有一处 wrap
func main() { fmt.Printf("err: %+v", c())} func a() error { return errors.Wrap(fmt.Errorf("xxx"), "test")} func b() error { return a()} func c() error { return b()}
输出如下:
err: xxxtestmain.a /home/ll/project/Go-000/Week02/blog/wrap.go:14main.b /home/ll/project/Go-000/Week02/blog/wrap.go:18main.c /home/ll/project/Go-000/Week02/blog/wrap.go:22main.main /home/ll/project/Go-000/Week02/blog/wrap.go:10runtime.main /usr/local/go/src/runtime/proc.go:204runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1374
再看多处 wrap 的现象
func main() { fmt.Printf("err: %+v", c())} func a() error { return errors.Wrap(fmt.Errorf("xxx"), "a")} func b() error { return errors.Wrap(a(), "b")} func c() error { return errors.Wrap(b(), "c")}
可以看到每一处 wrap 都添加了一次堆栈信息
err: xxxamain.a /home/ll/project/Go-000/Week02/blog/wrap.go:14main.b /home/ll/project/Go-000/Week02/blog/wrap.go:18main.c /home/ll/project/Go-000/Week02/blog/wrap.go:22main.main /home/ll/project/Go-000/Week02/blog/wrap.go:10runtime.main /usr/local/go/src/runtime/proc.go:204runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1374bmain.b /home/ll/project/Go-000/Week02/blog/wrap.go:18main.c /home/ll/project/Go-000/Week02/blog/wrap.go:22main.main /home/ll/project/Go-000/Week02/blog/wrap.go:10runtime.main /usr/local/go/src/runtime/proc.go:204runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1374cmain.c /home/ll/project/Go-000/Week02/blog/wrap.go:22main.main /home/ll/project/Go-000/Week02/blog/wrap.go:10runtime.main /usr/local/go/src/runtime/proc.go:204runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1374
func Is(err, target error) bool { if target == nil { return err == target } // 通过反射判读 target 是否可以被比较 isComparable := reflectlite.TypeOf(target).Comparable() for { // 循环判断是否相等 if isComparable && err == target { return true } // 判断是否实现了 is 接口,如果有实现就直接判断 if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { return true } // 去判断是否实现了 unwrap 的接口,如果实现了就进行 unwrap if err = Unwrap(err); err == nil { return false } }}
和 is 的逻辑类似,就是不断的进行 unwrap 进行比较,只要有一个相同就返回,如果一直到底都不行就返回 false
func As(err error, target interface{}) bool { if target == nil { panic("errors: target cannot be nil") } val := reflectlite.ValueOf(target) typ := val.Type() if typ.Kind() != reflectlite.Ptr || val.IsNil() { panic("errors: target must be a non-nil pointer") } if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) { panic("errors: *target must be interface or implement error") } targetType := typ.Elem() for err != nil { if reflectlite.TypeOf(err).AssignableTo(targetType) { val.Elem().Set(reflectlite.ValueOf(err)) return true } if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { return true } err = Unwrap(err) } return false
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。