前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go errors

Go errors

作者头像
一行舟
发布2022-08-25 14:15:11
4580
发布2022-08-25 14:15:11
举报
文章被收录于专栏:一行舟一行舟

Hi,我是行舟,今天和大家一起学习 Go 语言错误处理包:errors。

Go 语言自身的 errors:https://golang.google.cn/pkg/errors/ 包实现非常简单,使用起来非常灵活,但是又有很多不足。我们在分析 Go 语言 errors 包的同时,也介绍下一个开源的 errors 包:https://pkg.go.dev/github.com/pkg/errors。

语言内置 errors

Go 语言 errors 包对外暴露了四个方法:

func As(err erros, target any) bool

func Is(err, target error) bool

func New(text string) error

func Unwrap(err error) error

As(err errors, target any) bool

As 方法的作用是在第一个参数 err 的调用链中寻找和第二个参数 target 类型相同的的错误,并把找到的第一个 err 的值赋给 target ,然后返回 true;如果没有找到则返回 false。

代码语言:javascript
复制
import (
 "errors"
 "fmt"
 "io/fs"
 "os"
)

type MyError struct {
 Op   string
 Path string
 Err  error
}

func (e *MyError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

func main() {
 if _, err := os.Open("non-existing"); err != nil {
  var pathError *fs.PathError
  var myError *MyError
  if errors.As(err, &pathError) {
   fmt.Println("PathError:", pathError.Path) // PathError: non-existing
  }
  if errors.As(err, &myError) { // 在 err 的调用链中,没有找到 MyError 类型
   fmt.Println("MyError:", pathError.Path) // 没有打印任何结果
  }

  fmt.Println(err)
 }
}

Is(err errors, target any) bool

相比较于 As 方法,Is 只判断第一个参数 err 的调用链中是否存在和第二个参数 target 类型相同的的错误,存在则返回 true,否则返回 false。

New(text string) error

New 方法返回 error 类型,同时指定文本内容为传入的 text 值。

代码语言:javascript
复制
import (
 "errors"
 "fmt"
)

func main() {
 
 err1 := errors.New("I am an error.")
 err2 := errors.New("I am an error.")

 fmt.Println(err1 == err2) // false
}

上例中,我们定义了字符串相同的两个 error,在比较时,他们是不相等的。

看一下 error 包中 New 方法的实现:

代码语言:javascript
复制
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
}

可以看到 New 方法每次返回的是指针地址,所以每次调用返回的都是不同的地址。

Unwrap(err error) error

这个方法对于初次接触 Go 语言的同学其实有点懵。要理解为什么有 Unwrap 方法,首先需要知道 Wrap errors。当我们在代码中想要对原有错误进行扩展时,通常可以使用两种方式:

  1. 通过 newError := fmt.Errof("some reasons: %v", err) 方法对 err 进行包装,返回新的 error。
  2. 自己定义一个类型,其中的一个属性属于 error 类型,比如:
代码语言:javascript
复制
type MyError struct {
 Op   string
 Path string
 Err  error
}

其中 fmt.Errof() 方法,在使用 %w 时,返回了一个 Wrapping error。

代码语言:javascript
复制
import (
   "errors"
   "fmt"
)

func main() {

   err1 := errors.New("I am err1.")
   err2 := fmt.Errorf("I am err2 , %w", err1) // 通过 %w 可以生成一个 Wrapping error

   fmt.Println(errors.Unwrap(err2)) // I am err1.

}

Go 语言本身提供了 Unwrap 方法,并没有提供直接的 Wrap 方法,感觉还是挺奇怪的。

不过有一个 error 的开源仓库实现了,error的一些扩展方法。

开源 errors

仓库地址:https://pkg.go.dev/github.com/pkg/errors

它实现了更加丰富的 error 方法。

主要介绍下 Wrap 方法,Wrap 方法用来包装原来的 err 并添加扩展信息。

代码语言:javascript
复制
func ReadFile(path string)([]byte, error){
    f, err := os.Open(path)
    if err != nil{
        return nil, errors.Wrap(err, "open failed!") // 通过 Wrap 包装 err,既可以把保留原始错误,又可以添加其他信息
    }
    defer f.Close()
}

func main(){
    _, err := ReadConfig()
    if err != nil {
        fmt.Printf("original error: %T %v \n", errors.Cause(err), errors.Cause(err)) // 打印报错的地方 Cause 方法的返回值,还可以判断根因类型
        fmt.Printf("stack trace: \n %+v \n", err) // 打印错误堆栈
        os.Exit(1)
    }
}

func ReadConfig()([]byte, error){
    home := os.Getenv("home")
    config, err := ReafFile(......)
    return config, errors.WithMessage(err, "could not read config") // Wrap 会保存堆栈信息,WithMessage不会保存堆栈信息。
}

Wrap errors 是 Go 语言中处理错误很优雅的方法,但是最好是指在开发具体应用代码时使用,基础包不适合使用,因为如果你在基础包里调用了 Wrap ,业务方再调用 Wrap 会导致保留了两次堆栈信息。

为什么需要 Wrap errors ?因为当我们调用基础包返回 error 时,为了方便问题的定位和追踪,往往需要再添加一些提示信息,如果修改原始 error 的提示信息,会导致原始错误提示信息内容被覆盖。Wrap errors 可以既保留原始的 error 链,又能扩展错误提示。

error 使用分类

根据 error 的使用形式,我们将其分为三类:

  1. Sentinel Error
  2. error types
  3. Opaque errors
Sentinel Error

包级别的错误,比如 io 包中的定义。

代码语言:javascript
复制
var EOF = errors.New("EOF")

在使用时,我们只能像下面这样判断错误的类型。

代码语言:javascript
复制
if err == io.EOF {
 //...
}

if errors.Is(err, io.EOF){
 //...
}

这种定义错误的方式,需要把 error 当作 API 的一部分暴露出来,不是很优雅。

error types

自定义错误类型,在使用时通过类型断言,获取更多的上下文信息。

代码语言:javascript
复制
type MyErrot 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 happened", "server.go", 42}
}

func main(){
    err := test()
    switch err := err.(type){
    case nil:
        // do something success!
    case *MyError:
        fmt.Println("error occurred on line:", err.Line)
    default:
        // do something else.
    }
}

缺点:通过类型断言和类型 switch,需要让自定义的 error 变成 public。这种模型会导致与调用者产生强耦合,从而导致 API 变得脆弱。

虽然比 Sentinel Error 好一些,但是还是不建议使用,或者说在使用时要避免成为公共 API 的一部分。

Opaque errors

灵活的错误处理方式,要求代码和调用者之间的耦合最少。

虽然你知道发生了错误,但是你没有能力看到错误的内部。作为调用者,只关心操作结果就好了。

e.g

代码语言:javascript
复制
func fn() error {
    x,err := bar.Foo()
    if err != nil { // 不用关心错误细节
        return err
    }
}

有时候二分错误无法满足,需要更多判断,这种情况下我们可以断言错误实现了特定的行为,而不是断言错误是特定的类型或值。

代码语言:javascript
复制
// 封装方
type Error interface{
    error
    Timeout() bool // 是否超时
    Temporary() bool // 是否临时
}

type temporary interface {
    Temporary() bool
}

func IsTemporary(err error) bool {
    te, ok := err.(temporary)
    return ok && te.Temporary()
}

// 调用方
func main(){
    if nerr, ok := err.(Error) && nerr.Temporary() {
        time.Sleep(100)
        continue
    }
    if err != nil {
        log.Fatal(err)
    }
}

这是非常推荐的一种错误处理方式。

相关链接

https://golang.google.cn/pkg/errors/

https://pkg.go.dev/github.com/pkg/errors

https://blog.csdn.net/puss0/article/details/116489846

https://u.geekbang.org/lesson/324?article=481322

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一行舟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 语言内置 errors
    • As(err errors, target any) bool
      • Is(err errors, target any) bool
        • New(text string) error
      • Unwrap(err error) error
      • 开源 errors
      • error 使用分类
      • 相关链接
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档