Golang 如何使用正确使用error?

有很多种方法来声明 errors:

  • errors.New 声明简单的静态字符串错误信息
  • fmt.Errorf 声明格式化的字符串错误信息
  • 为自定义类型实现 Error() 方法
  • 通过 "pkg/errors".Wrap 包装错误类型

1.如何自定义错误类型?

    客户需要检测并处理此错误吗?如果是,那应该自定义类型,并实现 Error() 方法。

type errNotFound struct {
}
func (fe *errNotFound) Error() string {
    return "找不到文件"
}
//模拟错误
func openFile() ([]byte, error) {
    return nil, &errNotFound{}
}
func main() {
    _, err := openFile()
    if err != nil {
        fmt.Println(err)
    }
}
# 找不到文件

     当你需要捕获一些错误的具体信息,往往也需要通过自定义的方式,比如下面这个例子捕获哪个文件打开错误了

type errNotFound struct {
    file string
}
func (e errNotFound) Error() string {
    return fmt.Sprintf("file %q not found", e.file)
}
func open(file string) error {
    return errNotFound{file: file}
}
func use() {
    if err := open("test.txt"); err != nil {
        if err, ok := err.(errNotFound); ok {
            fmt.Println(err)
        } else {
            panic("unknown error")
        }
    }
}
func main() {
    use()
}
# file "test.txt" not found

    直接将自定义的错误类型设为导出需要特别小心,因为这意味着他们已经成为包的公开 API 的一部分了。更好的方式是暴露一个匹配函数来检测错误。  

type errNotFound struct {
  file string
}
func (e errNotFound) Error() string {
  return fmt.Sprintf("file %q not found", e.file)
}
func IsNotFoundError(err error) bool {
  _, ok := err.(errNotFound)
  return ok
}
func Open(file string) error {
  return errNotFound{file: file}
}
// package bar
if err := foo.Open("foo"); err != nil {
  if foo.IsNotFoundError(err) {
    // handle
  } else {
    panic("unknown error")
  }
}

2.当你需要定义一个简单的错误信息,可以用errors.New也可以用fmt.Errorf

     如果项目中仅仅是为了打日志,就可以直接用了,如果项目中需要捕获具体的错误并进行对应的处理,规范的做法是先声明这个错误类型

比较优秀的做法是这样做,切记不要通过Error()判断字符串这种方式去识别错误

var ErrCouldNotOpen = errors.New("could not open")
func Open() error {
  return ErrCouldNotOpen
}
if err := foo.Open(); err != nil {
  if err == ErrCouldNotOpen {
    // handle
  } else {
    panic("unknown error")
  }
}

    errors.New本质上也可以通过自定义模拟实现

func New(text string) error {
    return &errorString{text}
}
type errorString struct {
    s string
}
func (e *errorString) Error() string {
    return e.s
}
func test() error {
    return New("测试NewError")
}
func main() {
    err := test()
    fmt.Printf("%v", err)
}
# 测试NewError

3. 实际项目中,大量的if err != nil { log.Error(...)....}充斥在代码中

    一、可读性较差

    二、大量重复日志,给运维带来压力

  三、很难一次定位错误位置

    可以通过 "pkg/errors"包中提供的 Wrap、WithMessage、Cause等方法解决这个问题

    Wrap 可以包装信息加堆栈信息、WithMessage只包装信息、Cause可以找到原始错误

func inner() error {
    return errors.Wrap(sql.ErrNoRows, "inner failed")
}
func out() error {
    return errors.WithMessage(inner(), "out failed")
}
func main() {
    err := out()
    if err != nil {
        fmt.Printf("%+v
", err)
    }
    # sql: no rows in result set
    # inner failed
    # ...test.go:11
    # ...test.go:14
    # ...test.go:17
    # ...
    # out failed
    if errors.Cause(err) == sql.ErrNoRows {
        # true
    }
}

参考:

https://qcrao.com/2019/09/18/golang-error-break-through/

https://zhuanlan.zhihu.com/p/98152645

原文地址:https://www.cnblogs.com/peterleee/p/13890893.html