写程序中难免会遇到 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, " %+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 %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 接口的,里面也有互相转换的方法。