Go-errors第三方包学习详解编程语言

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

这样对于调试代码获得的信息非常有限, 所以这里安装了一个 第三方 error 包 IT虾米网

首先是 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 工具验证一下

Go-errors第三方包学习详解编程语言

 可以看到 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 接口的,里面也有互相转换的方法。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/18126.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论