这个包是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:没有找到该文件目录
}
error VS Exception
Go的处理异常逻辑是不引入Exception,他支持多参数返回, 所以你很容易的在函数的签名中带上实现了error interface的对象, 交给调用者来判定. 如果一个函数返回了Value,error. 你不能对着干value做任何假设,必须先要判定Error.
对于真正意外的情况, 那些表示不可恢复的程序错误:例如: 索引越界, 堆栈退出,程序初始化dao层失败, 读取Apollo配置失败等问题,我们才会推荐使用panic. 对于其他的error错误,我们应该是期望使用error来进行判定.
‘’
Sentienl Error (哨兵Error)
对于预定义的特定错误,我们叫它为 sentinel error (哨兵Error),这个名字来源于计算机编程中使用一个特定值来表示不可能进行进一步处理的做法。
对于Sentienl Error的例子可以参考 io.EOF,如下图:
**使用sentinel值是最不灵活的错误处理策略** 因为调用方必须使用==将结果和预先声明的值进行比较。
sentinel的缺点:
-
sentinel Erroor会成为你API公共的一部分
-
sentinel errors在两个包之间创造了依赖
sentinel errors 最糟糕的问题是它们在两个包之间创建了源代码依赖关系。例如,检查错误是否等于 io.EOF,您的代码必须导入 io 包。这个特定的例子听起来并不那么糟糕,因为它非常常见,但是想象一下,当项目中的许多包导出错误值时,存在耦合,项目中的其他包必须导入这些错误值才能检查特定的错误条件(in the form of an import loop)。
结论: 我们要尽可能地避免 sentinel errors.
Error Type
Error Type 是**实现了 error接口的自定义类型** 例如: 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()
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)
}
}
和错误值相比,错误类型(error type)的一大改进就是他们能够包装底层错误以及提供更多的上下文,官方pkg的 os.pathError 就是一个很好的例子
如下面代码:
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 errors
Opaque error 应该是最灵活的错误处理策略,因为他要求代码和调用者之间的耦合最少
func fn()error {
x,err := bar.Foo()
if err != nil {
return err
}
return nil
}
Handling Error
Golang的Error Handling一直备受争议,官方和社区也持续提出各项改进方案。作为语言级别的error支持,Go对error的实现却异常简单,甚至可以说是简陋。那么,到底什么样的做法才是Golang Error Handling的最佳实践呢?
无错误的正常流程代码,将成为一条直线,不是缩进的代码
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)
}
Wrap errors
通过上下文打印出堆栈信息
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把堆栈信息打印出来
##### Golang 1.1.3
在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)
}



