Go-errors第三方包学习

写程序中难免会遇到 error 类型的值, 对于处理 或者 创建 error 的方法, go 标准库里 只有简单的 error.Error() 返回 string (错误的文本信息),

这样对于调试代码获得的信息非常有限, 所以这里安装了一个 第三方 error 包 github.com/pkg/errors

首先是 new 方法

Go 语言使用 error 类型来返回函数执行过程中遇到的错误,如果返回的 error 值为 nil,则表示未遇到错误,否则 error 会返回一个字符串,用于说明遇到了什么错误。通俗的说,error就是一个接口而已,定义如下:

type error interface {
    Error() string

}

New方法
将字符串 text 包装成一个 error 对象返回
New returns an error that formats as the given text.

func New(text string) error

例子:
看看io.go中的定义:

var ErrShortWrite    = errors.New("short write")
var ErrShortBuffer   = errors.New("short buffer")
var EOF              = errors.New("EOF")
var ErrUnexpectedEOF = errors.New("unexpected EOF")
var ErrNoProgress    = errors.New("multiple Read calls return no data or error")

 

package main

import (
   "fmt"
   "github.com/pkg/errors"
)

func main() {
   err := errors.New("create error")
   fmt.Printf("%+v", err)
}

输出:

create error
main.main
    /Users/qq/Desktop/code/go/pkgErrors/main.go:9
runtime.main
    /usr/local/go/src/runtime/proc.go:200
runtime.goexit
    /usr/local/go/src/runtime/asm_amd64.s:1337
Process finished with exit code 0

显然 相对于 go 标准库里的 error ,这里创建的 error 更详细, 主要是打印出了堆栈

来看一下 new 函数做了些什么

func New(message string) error {
   return &fundamental{
      msg:   message,
      stack: callers(),
   }
}

可以看到 new 函数 接受一个 string 代表你需要展示的错误描述, 返回一个实现了 error 接口的结构体 fundamental

其中 fundamental 结构体的 stack 调用了 callers() 方法, 正是这个方法,才能让我们的 error 能返回 错误的堆栈信息

func callers() *stack {
   const depth = 32
   var pcs [depth]uintptr
   n := runtime.Callers(3, pcs[:])
   var st stack = pcs[0:n]
   return &st
}

注意, 这里 callers()只是调用了 runtime.Callers 拿到了对应的指针,并没有拿到对应的文件名和行, 并且这里的 depth 值限制了 最多保存 32 次堆栈

可以用 debug 工具验证一下

 

 可以看到 stack 只是一个保存了指针的数组, 那么为什么 format 格式化输出的时候就出现了详细的 文件名和行号呢?

 

重点2

fundamental 实现了 Format 方法, 让我们的格式化输出不打印原本的结构体, 转而输出这个方法想输出的东西

func (f *fundamental) Format(s fmt.State, verb rune) {
   switch verb {
   case 'v':
      if s.Flag('+') {
         io.WriteString(s, f.msg)
         f.stack.Format(s, verb)
         return
      }
      fallthrough
   case 's':
      io.WriteString(s, f.msg)
   case 'q':
      fmt.Fprintf(s, "%q", f.msg)
   }
}

刚才我们知道 堆栈信息保存在 stack 这个成员属性里面, 所以看下 stack.Format 做了些什么

func (s *stack) Format(st fmt.State, verb rune) {
   switch verb {
   case 'v':
      switch {
      case st.Flag('+'):
         for _, pc := range *s {
            f := Frame(pc)
            fmt.Fprintf(st, "\n%+v", f)
         }
      }
   }
}

可以看到 嵌套的很深, 我们在第二个 format 方法依然没看到具体 获取文件名的方法, 可以看到 这个 format 只针对 %+v 这种做处理了, 这也是为什么我们只能通过 %+v 才能打印到堆栈信息的原因, 这里将我们的 指针数组做出了循环, 然后每个指针去创建了 Frame 的结构体

func (f Frame) Format(s fmt.State, verb rune) {
   switch verb {
   case 's':
      switch {
      case s.Flag('+'):
         pc := f.pc()
         fn := runtime.FuncForPC(pc)
         if fn == nil {
            io.WriteString(s, "unknown")
         } else {
            file, _ := fn.FileLine(pc)
            fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
         }
      default:
         io.WriteString(s, path.Base(f.file()))
      }
   case 'd':
      fmt.Fprintf(s, "%d", f.line())
   case 'n':
      name := runtime.FuncForPC(f.pc()).Name()
      io.WriteString(s, funcname(name))
   case 'v':
      f.Format(s, 's')
      io.WriteString(s, ":")
      f.Format(s, 'd')
   }
}

终于找到 熟悉的 runtime.FuncForPc() 和 FileLine 函数了

代码量不算多, 但是通过接口的这种隐形关系, 能自动帮我们实现这种实用的功能真的很棒。

对于这个包还有个好处是 他是可以兼容 官方的 error 接口的,里面也有互相转换的方法。

posted @ 2020-01-14 16:40  大西瓜Paul  阅读(382)  评论(0编辑  收藏  举报
/*增加返回顶部按钮*/ 返回顶部 /*给标题增加蓝色背景长条*/