赞
踩
这个包是golang内置的error的包,errors包相关源码:
package errors // 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 }
Go error 就是一个普通的接口,普通的值;
我们会经常使用: errors.New() 来返回一个error对象。
有一点需要注意:
errors.New()返回的是内部errorString对象的指针.(&)
为什么New()出来的对象要返回 errorString的指针呢?
因为:
(https://blog.csdn.net/weixin_34258782/article/details/91929138)
**我们在调用errors.New("")来返回一个错误时, 可以通过比较指针,来比较error是否相等, 实际上就是控制相同的错误我们只创建一个error对象。否则对象复制一下,在比较就是false了。而且指针才会与nil相比较, 如果是字符串"" 虽然错误内容为空,但是还是有错误的。**
func main() {
NotFoundErr := errors.New("没有找到该文件目录")
fmt.Println(NotFoundErr) // type: *errors.errorString :Out:没有找到该文件目录
fmt.Println(NotFoundErr.Error()) // Type:string Out:没有找到该文件目录
}
对于真正意外的情况, 那些表示不可恢复的程序错误:例如: 索引越界, 堆栈退出,程序初始化dao层失败, 读取Apollo配置失败等问题,我们才会推荐使用panic. 对于其他的error错误,我们应该是期望使用error来进行判定.
‘’
对于Sentienl Error的例子可以参考 io.EOF,如下图:


sentinel Erroor会成为你API公共的一部分
sentinel errors在两个包之间创造了依赖
sentinel errors 最糟糕的问题是它们在两个包之间创建了源代码依赖关系。例如,检查错误是否等于 io.EOF,您的代码必须导入 io 包。这个特定的例子听起来并不那么糟糕,因为它非常常见,但是想象一下,当项目中的许多包导出错误值时,存在耦合,项目中的其他包必须导入这些错误值才能检查特定的错误条件(in the form of an import loop)。
结论: 我们要尽可能地避免 sentinel errors.
package main import "fmt" type MyError struct { Msg string File string Line int } func (e *MyError) Error() string { return fmt.Sprintf("%s:%d:%s",e.File,e.Line,e.Msg) } func test() error { return &MyError{"something happend","server.go",42} } func main() { err := test() if err != nil { fmt.Println(err) } }``` ```go
因为MyError是一个类型,所以调用者可以使用断言转换这个类型;来获取更多的上下文信息
package main import "fmt" type MyError struct { Msg string File string Line int } func (e *MyError) Error() string { return fmt.Sprintf("%s:%d:%s",e.File,e.Line,e.Msg) } func test() error { return &MyError{"something happend","server.go",42} } func main() { err := test() switch err := err.(type) { case nil: fmt.Println("没有error") case *MyError: fmt.Println("error occureed on line:",err.Line) } }
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }```
-使用Error Type的建议:
调用者要使用类型断言和类型switch,就是要让自定义的error变为public。这种模型回导致和调用者产生强耦合,从而导致API变得脆弱。
使用Error Type的结论:
尽量避免使用 error types,虽然错误类型比sentinel errors更好,因为他们可以捕获关于出错更多的上下文,但是error types 共享error values的许多相同问题,因为建议是避免使用,或者至少避免将他们作为公共API的一部分。
Opaque error 应该是最灵活的错误处理策略,因为他要求代码和调用者之间的耦合最少
func fn()error {
x,err := bar.Foo()
if err != nil {
return err
}
return nil
}
无错误的正常流程代码,将成为一条直线,不是缩进的代码
f,err := os.Open(path)
if err != nil {
// bandle error
}
// do stuff
f,err := os.Open(path_
if err == nil {
// do stuff
}
// handle error
Eliminate error handling by eliminating errors
(通过消除错误来消除错误处理)
例如下面的例子:
处理前:
func Auth(r *Request) error {
err := authenticate(r.user)
if err != nil {
return err
}
return nil
}
处理后:
func Auth(r *Request) error {
return authticater(r.user)
}
通过上下文打印出堆栈信息
func ReadFile(path string)([]byte,error){ if err != nil{ return nil,errors.Wrap(err,"open failed") } } func ReadConfig()([]byte,error){ config,err := ReadFile("ddd") return config ,errors.WithMessage(err,"cound not read config") } func main(){ _,err := ReadConfig() if err != nil{ fmt.println(err) } }
如果和其他库进行协作,考虑使用errors.Wrap 或是errors.Wrapf来保存堆栈信息
直接返回错误而不是在每个产生错误的地方打印日志
在程序顶部使用%+v把堆栈信息打印出来
在go1.13 之后,为 errors 和 fmt 标准库包引入了新特性,以简化处理包含其他错误的错误。其中最重要的是: 包含另一个错误的 error 可以实现返回底层错误的 Unwrap 方法。如果 e1.Unwrap() 返回 e2,那么我们说 e1 包装 e2,您可以展开 e1 以获得 e2。按照此约定,我们可以为上面的 QueryError 类型指定一个 Unwrap 方法,该方法返回其包含的错误:
同时为errors包添加了3个工具函数,他们分别是Unwrap、Is和As,先来聊聊Unwrap。
func (e *QueryError)Unwrap() error {
return e.Err
}
if errros.Is(err,ErrNotFound){
}
if errors.As(err,&e){
}
例子1:
func main() {
e := errors.New("原始错误e")
w := fmt.Errorf("Wrap了一个错误%w", e)
fmt.Println(errors.Unwrap(w))
}

以上这个例子,通过errors.Unwrap(w)后,返回的其实是个e,也就是被嵌套的那个error。 这里需要注意的是,嵌套可以有很多层,我们调用一次errors.Unwrap函数只能返回最外面的一层error,如果想获取更里面的,需要调用多次errors.Unwrap函数。最终如果一个error不是warpping error,那么返回的是nil。
Unwrap的源码
func Unwrap(err error) error {
//先判断是否是wrapping error
u, ok := err.(interface {
Unwrap() error
})
//如果不是,返回nil
if !ok {
return nil
}
//否则则调用该error的Unwrap方法返回被嵌套的error
return u.Unwrap()
}
####### error包内的 IS 函数
这样我们就可以通过判断来做一些事情。但是现在有了wrapping error后这样办法就不完美的,因为你根本不知道返回的这个err是不是一个嵌套的error,嵌套了几层。所以基于这种情况,Golang为我们提供了errors.Is函数。
func Is(err, target error) bool
如果err和target是同一个,那么返回true
如果err 是一个wrap error,target也包含在这个嵌套error链中的话,那么也返回true。
IS源码demo(很简单的一个函数,要么咱俩相等,要么err包含target,这两种情况都返回true,其余返回false)
func Is(err, target error) bool { if target == nil { return err == target } isComparable := reflectlite.TypeOf(target).Comparable() //for循环,把err一层层剥开,一个个比较,找到就返回true for { if isComparable && err == target { return true } //这里意味着你可以自定义error的Is方法,实现自己的比较代码 if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { return true } //剥开一层,返回被嵌套的err if err = Unwrap(err); err == nil { return false } } }
####### AS 函数
在Go 1.13之前没有wrapping error的时候,我们要把error转为另外一个error,一般都是使用type assertion 或者 type switch,其实也就是类型断言。
例如:
if perr, ok := err.(*os.PathError); ok {
fmt.Println(perr.Path)
}
比如例子中的这种方式,但是现在给你返回的err可能是已经被嵌套了,甚至好几层了,这种方式就不能用了,所以Golang为我们在errors包里提供了As函数,现在我们把上面的例子,用As函数实现一下。
var perr *os.PathError
if errors.As(err, &perr) {
fmt.Println(perr.Path)
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。