Go error是一个很痛的话题(真心难用)

标准库 error 的定义

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {Error() string
}

error 是一个内置的接口,它拥有一个Error() string方法,实现该方法即实现了error接口。

标准库errors包

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.// Package errors implements functions to manipulate errors.
//
// The New function creates errors whose only content is a text message.
//
// The Unwrap, Is and As functions work on errors that may wrap other errors.
// An error wraps another error if its type has the method
//
//  Unwrap() error
//
// If e.Unwrap() returns a non-nil error w, then we say that e wraps w.
//
// Unwrap unpacks wrapped errors. If its argument's type has an
// Unwrap method, it calls the method once. Otherwise, it returns nil.
//
// A simple way to create wrapped errors is to call fmt.Errorf and apply the %w verb
// to the error argument:
//
//  errors.Unwrap(fmt.Errorf("... %w ...", ..., err, ...))
//
// returns err.
//
// Is unwraps its first argument sequentially looking for an error that matches the
// second. It reports whether it finds a match. It should be used in preference to
// simple equality checks:
//
//  if errors.Is(err, fs.ErrExist)
//
// is preferable to
//
//  if err == fs.ErrExist
//
// because the former will succeed if err wraps fs.ErrExist.
//
// As unwraps its first argument sequentially looking for an error that can be
// assigned to its second argument, which must be a pointer. If it succeeds, it
// performs the assignment and returns true. Otherwise, it returns false. The form
//
//  var perr *fs.PathError
//  if errors.As(err, &perr) {//      fmt.Println(perr.Path)
//  }
//
// is preferable to
//
//  if perr, ok := err.(*fs.PathError); ok {//      fmt.Println(perr.Path)
//  }
//
// because the former will succeed if err wraps an *fs.PathError.
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
}

errors.New(text string) error,创建一个error对象

errors 包中定义了 errorString类型,其实现了error接口,errors.New(text string)函数,根据传入字符串返回一个error对象,我们可以看出,其返回值是指针类型,这是为了区分error,即使两个传入的text文本一致,那么返回的值也是完全不同的。

errors.Unwrap(err error) error 拆箱error

如果传入的error实现了

interface {Unwrap() error
}

接口,则调用Unwrap方法对传入的error进行拆箱,并返回拆箱后的error,否则返回nil

errors.Is(err, target error) bool 判定err是否为target error

该函数会调用Unwrap方法,获取最底层的错误原因

errors.As(err error, target any) bool 判定err链中是否包含传入的target,若包含则用target带出来

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package errorsimport ("internal/reflectlite"
)// 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()
}// Is reports whether any error in err's chain matches target.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
//
// An error type might provide an Is method so it can be treated as equivalent
// to an existing error. For example, if MyError defines
//
//  func (m MyError) Is(target error) bool { return target == fs.ErrExist }
//
// then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for
// an example in the standard library. An Is method should only shallowly
// compare err and the target and not call Unwrap on either.
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}}
}// As finds the first error in err's chain that matches target, and if one is found, sets
// target to that error value and returns true. Otherwise, it returns false.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// An error type might provide an As method so it can be treated as if it were a
// different error type.
//
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
func As(err error, target any) 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")}targetType := typ.Elem()if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {panic("errors: *target must be interface or implement error")}for err != nil {if reflectlite.TypeOf(err).AssignableTo(targetType) {val.Elem().Set(reflectlite.ValueOf(err))return true}if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {return true}err = Unwrap(err)}return false
}var errorType = reflectlite.TypeOf((*error)(nil)).Elem()

Go 1.13 fmt.Errorf %w谓词,包装error

   err := fmt.Errorf("access denied: %w", ErrPermission) // 加入上下文信息// ...if errors.Is(err,ErrPermission) { //可以用Is 和 As 进行判定// ...}var err1 *ErrPermissionif errors.As(err,&ErrPermission) {// ...}

令人不解的是,直接在errors中加入Wrap函数,统一API不香吗?

Error的几种处理形式

Sentinel errors 哨兵error

   var EOF = errors.New("EOF")

包级别的预定义的Error称之为哨兵error,这种error无法携带更多的上下文信息,并且需要包中暴露更多的API,容易产生更多的依赖,不建议使用。

Error types 自定义Error类型

可以返回

type MyError struct {Message  stringFileName stringFileLine int
}func (m *MyError) Error() string {return fmt.Sprintf("message:%s,file:%s,line:%d", m.Message, m.FileName, m.FileLine)
}func forTest() error {return &MyError{Message:  "not found ... ",FileName: "msg.go",FileLine: 100,}
}

检测error的时候可以使用断言的方式

switch forTest().(type) {case nil://...
case *MyError://...
default://...
}if err, ok := forTest().(*MyError); ok {// do something ...
}

标准库中也有很多使用自定义Error类型的场景
如: src/io/fs.go

// PathError records an error and the operation and file path that caused it.
type PathError struct {Op   stringPath stringErr  error
}func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }func (e *PathError) Unwrap() error { return e.Err }

自定义的Error类型虽然能携带更多的上下文信息,但是其与哨兵error类似的是,依然会产生大量API的暴露,增加了维护的心智负担,所以依然不建议使用。

Opaque errors 不透明的error处理

不透明的error处理即只使用if err != nil去判断,而不关心其底层error的类型与值。调用者可以更加专注于函数的返回结果。这种error处理方式限制了上下文信息的携带。

func fn() error {result, err := Function()if err != nil {return err}// use result ...
}

一种使用姿势

Assert errors for behaviour, not type 断言成一种行为,而非强类型
定义一个包内可见的接口

    type readAble interface{CanRead() bool}

定义一个包级别的函数作为暴露的API

    type IsCanRead(err error) bool {r, ok := err.(readAble)return ok && r.CanRead()}

error的优雅处理姿势

统计io.Reader读取内容的行数

func CountLines(r io.Reader) (int, error) {var (br = bufio.NewReader(r)lines interr error)for {_, err = br.ReadString('\n')lines++if err != nil {break}  }if err != io.EOF {return 0, err}return lines, nil
}

改进版本,利用bufio的Scanner

func CountLines(r io.Reader) (int, error) {sc := bufio.NewScanner(r)lines := 0for sc.Scan() {lines++}return lines, sc.Err()
}


对上述代码的改进

Wrap Errors 打包Error

   err = fmt.Errorf("msg: %v",err)

github.com/pkg/errors 这个库用起来十分舒适,Go1.13+标准库errors参照了该库的设计理念,该库可以支持携带堆栈信息,同时pkg/errors兼容了Go1.13+ 的Unwrap,Is,As方法。

// Package errors provides simple error handling primitives.
//
// The traditional error handling idiom in Go is roughly akin to
//
//     if err != nil {//             return err
//     }
//
// which when applied recursively up the call stack results in error reports
// without context or debugging information. The errors package allows
// programmers to add context to the failure path in their code in a way
// that does not destroy the original value of the error.
//
// Adding context to an error
//
// The errors.Wrap function returns a new error that adds context to the
// original error by recording a stack trace at the point Wrap is called,
// together with the supplied message. For example
//
//     _, err := ioutil.ReadAll(r)
//     if err != nil {//             return errors.Wrap(err, "read failed")
//     }
//
// If additional control is required, the errors.WithStack and
// errors.WithMessage functions destructure errors.Wrap into its component
// operations: annotating an error with a stack trace and with a message,
// respectively.
//
// Retrieving the cause of an error
//
// Using errors.Wrap constructs a stack of errors, adding context to the
// preceding error. Depending on the nature of the error it may be necessary
// to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface
//
//     type causer interface {//             Cause() error
//     }
//
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
// the topmost error that does not implement causer, which is assumed to be
// the original cause. For example:
//
//     switch err := errors.Cause(err).(type) {//     case *MyError:
//             // handle specifically
//     default:
//             // unknown error
//     }
//
// Although the causer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported:
//
//     %s    print the error. If the error has a Cause it will be
//           printed recursively.
//     %v    see %s
//     %+v   extended format. Each Frame of the error's StackTrace will
//           be printed in detail.
//
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface:
//
//     type stackTracer interface {//             StackTrace() errors.StackTrace
//     }
//
// The returned errors.StackTrace type is defined as
//
//     type StackTrace []Frame
//
// The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
//     if err, ok := err.(stackTracer); ok {//             for _, f := range err.StackTrace() {//                     fmt.Printf("%+s:%d\n", f, f)
//             }
//     }
//
// Although the stackTracer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// See the documentation for Frame.Format for more details.
package errorsimport ("fmt""io"
)

errors.New(message string) error 创建error

// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {return &fundamental{msg:   message,stack: callers(),}
}

errors.Errorf(format string, args …interface{}) error 格式化Error信息

// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {return &fundamental{msg:   fmt.Sprintf(format, args...),stack: callers(),}
}

fundamental 包内结构体,包含错误信息和堆栈结构

// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct { //基础结构msg string*stack
}func (f *fundamental) Error() string { return f.msg } // 实现error接口func (f *fundamental) Format(s fmt.State, verb rune) { // 实现Formatter接口支持 %v,%+v,%s,%q几个谓词switch verb {case 'v':if s.Flag('+') {io.WriteString(s, f.msg)f.stack.Format(s, verb)return}fallthroughcase 's':io.WriteString(s, f.msg)case 'q':fmt.Fprintf(s, "%q", f.msg)}
}

WithStack打包堆栈信息

// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {if err == nil {return nil}return &withStack{err,callers(),}
}type withStack struct {error*stack
}func (w *withStack) Cause() error { return w.error }// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withStack) Unwrap() error { return w.error }func (w *withStack) Format(s fmt.State, verb rune) {switch verb {case 'v':if s.Flag('+') {fmt.Fprintf(s, "%+v", w.Cause())w.stack.Format(s, verb)return}fallthroughcase 's':io.WriteString(s, w.Error())case 'q':fmt.Fprintf(s, "%q", w.Error())}
}

errors.func Wrap(err error, message string) error 打包自定义的信息

// 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(),}
}

errors.func Wrapf 打包格式化的自定义的信息

// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {if err == nil {return nil}err = &withMessage{cause: err,msg:   fmt.Sprintf(format, args...),}return &withStack{err,callers(),}
}

withMessage 打包自定义信息

// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {if err == nil {return nil}return &withMessage{cause: err,msg:   message,}
}// WithMessagef annotates err with the format specifier.
// If err is nil, WithMessagef returns nil.
func WithMessagef(err error, format string, args ...interface{}) error {if err == nil {return nil}return &withMessage{cause: err,msg:   fmt.Sprintf(format, args...),}
}type withMessage struct {cause errormsg   string
}func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } // 实现error 接口
func (w *withMessage) Cause() error  { return w.cause } // 根因// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withMessage) Unwrap() error { return w.cause } // 拆箱获取根因func (w *withMessage) Format(s fmt.State, verb rune) {  // 格式化输出switch verb {case 'v':if s.Flag('+') {fmt.Fprintf(s, "%+v\n", w.Cause())io.WriteString(s, w.msg)return}fallthroughcase 's', 'q':io.WriteString(s, w.Error())}
}

errors.Cause(err error) error 获取最底层错误原因(根因)

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
//     type causer interface {//            Cause() error
//     }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {type causer interface {Cause() error}for err != nil {cause, ok := err.(causer)if !ok {break}err = cause.Cause()}return err
}

在使用该库时应注意,最底层错误原因打包一次即可,否则会出现多倍相同的堆栈信息,所以应把控err返回值是否直接透传。

截止目前,该博文中标准库源码版本为Go 1.19.3, github.com/pkg/errors的版本为0.9.1。

Go 1.20 对标准库errors的新提案是:
增加errors.Join API
err = errors.Join(err1,err2)
同时修改errors.Unwrap API,让其返回[]error。
fmt.Errorf,errors.Is, errors.As也会做相应修改。
Reference
毛剑老师的《Go进阶训练营》
https://coolshell.cn/articles/21140.html

Go 1.19.3 error原理简析相关推荐

  1. Go 1.19.3 channel原理简析

    channel channel和goroutine是Go语言的核心命脉.这篇文章来简单介绍一下Go chan的原理,源码并不好读,应结合gmp调度模型来理解,后续补充吧. 由上图可见,chan的底层结 ...

  2. Go 1.19.3 select原理简析

    select 负责监听channel 其核心函数selectgo中使用了对地址的堆排序(大根堆),以及洗牌算法,保证case被执行到的概率接近均等.后续补充. select中case的结构 const ...

  3. Android Handler与Looper原理简析

    一直感觉自己简直就是一个弱智,最近越来越感觉是这样了,真的希望自己有一天能够认同自己,认同自己. 本文转载于:https://juejin.im/post/59083d7fda2f60005d14ef ...

  4. Webpack模块化原理简析

    webpack模块化原理简析 1.webpack的核心原理 一切皆模块:在webpack中,css,html.js,静态资源文件等都可以视作模块:便于管理,利于重复利用: 按需加载:进行代码分割,实现 ...

  5. grpc通信原理_gRPC原理简析

    gRPC原理简析 gRPC是由谷歌提出并开发的RPC协议,gRPC提供了一套机制,使得应用程序之间可以进行通信. 降级开发者的使用门槛,屏蔽网络协议,调用对端的接口就像是调用本地的函数一样.而gRPC ...

  6. Android V1及V2签名原理简析

    Android为了保证系统及应用的安全性,在安装APK的时候需要校验包的完整性,同时,对于覆盖安装的场景还要校验新旧是否匹配,这两者都是通过Android签名机制来进行保证的,本文就简单看下Andro ...

  7. CRC原理简析——史上最清新脱俗简单易懂的CRC解析

    CRC原理简析 1. CRC校验原理 CRC校验原理根本思想就是先在要发送的帧后面附加一个数(这个就是用来校验的校验码,但要注意,这里的数也是二进制序列的,下同),生成一个新帧发送给接收端.当然,这个 ...

  8. Java的定时器Timer和定时任务TimerTask应用以及原理简析

    记录:272 场景:Java JDK自带的定时器Timer和定时任务TimerTask应用以及原理简析.在JDK工具包:java.util中可以找到源码,即java.util.Timer和java.u ...

  9. 转子接地保护原理_发变组转子接地保护原理简析

    发变组转子接地保护原理简析 发电机转子接地故障是常见的故障之一, 发生一点接地, 对发电机本身并不直接构成危 害,此时可通过转移负荷,平稳停机后,再查故障点:若在此基础上又发生另外一点接地, 将会严重 ...

最新文章

  1. GitHub遭攻击滥用以代管网钓套件
  2. jq onclick 定义_从HTML中的onClick属性调用jQuery方法
  3. angular中的class写三元表达式 和 清空表单校验
  4. 1342. 断开的项链【难度: 一般 / 破环成链】
  5. MySQL杂记(更新时间——2014.05.23)
  6. 如何打开Cookies网页
  7. ARM系统中断产生流程
  8. Dev-C++的安装使用与介绍
  9. eclipse汉化教程(官方汉化包,傻瓜式操作,附带中英文快捷切换方式以及常见问题解决方案)
  10. Moodle安装完全手册
  11. MySQL经典50题目,必须拿下!
  12. 用计算机处理表格信息,怎么制作表格-三线表丨做数据表格必须学会的处理技巧...
  13. 各种说明方法的答题格式_说明文方法答题格式
  14. 基于FPGA的卷积神经网络加速器(绪论)
  15. js制作flash文件进度条
  16. 汉语语法与人工智能---数据结构+汉语语法
  17. 有关龙的成语(词语)、故事、诗歌
  18. 源码自动生成流程图软件介绍
  19. 转 一个游戏程序员的学习资料
  20. 在国外怎么观看国内网站视频电影电视剧

热门文章

  1. Java中的BigDecimal类你了解多少?
  2. 几款游戏引擎技术对比
  3. android xbox 手柄,Xbox One手柄将正式适配安卓9.0系统 《堡垒之夜》第一时间支持...
  4. 如何播放html文件类型,m3u8格式如何播放
  5. RecyclerView与ViewPager2
  6. dvwa-XSS(Reflected)
  7. ubuntu连不上校园网
  8. 将阿拉伯数字转换为中文大写数字 —— pyhton实现
  9. 宜信davinci搭建
  10. 宜信:提升与反思——实习的意义并不止于其本身