在Go语言中如何更详细地处理与包装错误
1.Go语言中的错误处理基础 在go语言中,我们使用error接口来处理错误,error接口的定义十分简单,只需要包含有 Error() string 方法即可。但如何从一层一层的程序中详细完整的传递,记录错误,以及如何去方便快捷的找到错误根源,显然是一个难点。本文就针对这个难点来发表一些浅薄的个人见解。(注:本文更注重于表述个人见解,如若对您有帮助,更建议结合个人项目搭建错误处理与日志体系) type error interface { Error() string } 我认为处理错误的最重要的两点在于错误链与调用栈,当一个error 发生时,我们需要在调用处知道完整的错误链,以及在错误发生处知道完整的调用栈,有了这两点我们就既可以得知环环相扣的错误信息,又能根据调用栈直截了当的知晓调用流程和错误源头。本篇文章不会对两者的结合展开叙述,只是单独的解释和演示相关机制。 2.Go1.13的错误包装机制 从Go 1.13开始,标准库提供了错误包装功能。你可以使用fmt.Errorf和%w格式化动词来包装错误,这样可以保留原始错误的上下文信息,同时添加更多描述。 if err := do(); err != nil { return fmt.Errorf("failed to do..., %w",err) } 这里给出应用错误链的一个简单demo。 func main() { err := do() e := errors.Unwrap(err) fmt.Printf("err: %v\n", err) fmt.Printf("origin: %v\n", e) } func do() error { if err := learn(); err != nil { return fmt.Errorf("do something failed, %w", err) } return nil } func learn() error { return fmt.Errorf("can`t learn") } 最终的输出如下: err: do something failed, can`t learn origin: can`t learn 看到这里相信你已经有了一些奇思妙想了,利用错误链的机制不仅可以传递错误信息,还因为Unwrap的存在而有了更多可能性,现在我来介绍如何自定义错误类型并传递额外信息。 3.自定义错误类型 首先,我们定义一个自己的结构体,和一个满足自己结构体方法的接口。 type Merror interface { Error() string Extra() any } type merror struct { msg string // 存储错误信息 extra int64 // 存储额外的信息(any type) } // New 创建一个新的结构体 func New(msg string, extra ...int64) error { e := &merror{ msg: msg, } if extra != nil { e.extra = extra[0] } return e } func (e *merror) Error() string { return e.msg } func (e *merror) Extra() any { return e.extra } // Format 用于进行格式化的输出,在遇到占位符时转换为我们想要的内容 func (e *merror) Format(state fmt.State, verb rune) { switch verb { case 's', 'v': io.WriteString(state, fmt.Sprintf("msg: %s,extra: %d", e.msg, e.extra)) } } 我们自定义的merror 实现了Error() string 与 Formatter(state,rune) 接口。对于前者,这意味着merror 可以被当成error 传递。对于后者,这让我们可以自己实现我们所想要的的输出格式。 ...