Golang实践-error
Error
Go error 是一个普通的接口,普通的值
type error interface {
Error() string
}
经常使用errors.New()来返回一个error对象
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
对于真正意外的情况,那些表示不可恢复的程序错误,比如索引越界,不可恢复的环境问题,栈溢出,才会使用panic。对于其他错误情况,应该期望使用error来进行判定
- 简单
- 考虑失败,而不是成功
- 没有隐藏的控制流
- 完全由你来控制
- Error are values
Error Type
Sentinel Error
预定义的特定错误,我们叫为 sentinel error,这个名字来源于计算机编程中使用一个特定值来表示不可能进行进一步处理的做法。所以对于 Go,我们使用特定的值来表示错误。
使用 sentinel 值是最不灵活的错误处理策略,因为调用方必须使用 == 将结果与预先声明的值进行比较。当您想要提供更多的上下文时,这就出现了一个问题,因为返回一个不同的错误将破坏相等性检查。
甚至是一些有意义的 fmt.Errorf 携带一些上下文,也会破坏调用者的 == ,调用者将被迫查看 error.Error() 方法的输出,以查看它是否与特定的字符串匹配。
不应该依赖检测 error.Error 的输出,Error 方法存在于 error 接口主要用于方便程序员使用,但不是程序(编写测试可能会依赖这个返回)。这个输出的字符串用于记录日志、输出到 stdout 等
- Sentinel Error 成为你API的一部分
- 如果公共函数或是方法返回一个特定的错误,那么该值比粗是公共的,当然要有文档记录,这会增加API的表面积
- 如果API定义了一个返回特定错误的interface{},那么该接口所有实现都需要被限制为只返回该错误,即使他们可以提供更具有描述性的错误
- Sentinel Errors在两个包之间创造了依赖
- 尽可能的避免sentinel errors
Error types
Error type 是实现了 error 接口的自定义类型。例如 MyError 类型记录了文件和行号以展示发生了什么
type MyError struct {
Msg string
File string
Line int
}
func (e *MsgError) Error() string {
return fmt.Sprintf("%s:%d:%s",e.FIle,e.Line,e.Msg)
}
func test() error {
return &MyError{"ddd","xxx",42}
}
与错误值相比,错误类型的一大改进是它们能够包装底层错误以提供更多上下文。一个不错的例子就是 os.PathError 他提供了底层执行了什么操作、那个路径出了什么问题。
type PathError struct {
Op string
Path string
Err error
}
Opaque errors
这是最灵活的错误处理策略,因为它要求代码和调用者之间的耦合最少
func fn() error {
x,err := bar.Foo()
if err != nil{
return err
}
return nil
}
Handling Error
无错误的正常流程,将成为一条直线,而不是缩进代码
f,err := os.Open(path)
if err != nil{
}
func CountLines(r io.Reader)(int,error){
sc := bufio.NewScanner(r)
lines := 0
for sc.Scan(){
lines++
}
return lines,sc.Err()
}
tpye errWriter struct {
io.Writer
err error
}
func (e *errWriter)Write(buf []byte)(int,error){
if e.err != nil{
return 0,e.err
}
var n int
n,e.err = e.Writer.Writer(buf)
return n,nil
}
func WriteResponse(w io.Writer,st Status, headers []header,body io.Reader) error {
ew := &errWriter{w}
if _,h := range headers {
fmt.Fprintf(ew,"%s:%s\r\n",h.Key,h.Value)
}
io.Copy(ew,body)
return ew.err
}
Wrap errors
通过上下文打印出堆栈的信息。
func ReadFile(path string)([]byte,error){
if err != nil{
return nil,errors.Wrap(err,"open failed")
}
}
func ReadConfig()([]byte,error){
config,err := ReadFile("ddd")
return config ,errors.WithMessage(err,"cound not read config")
}
func main(){
_,err := ReadConfig()
if err != nil{
fmt.println(err)
}
}
如果和其他库进行协作,考虑使用errors.Wrap 或是errors.Wrapf来保存堆栈信息
直接返回错误而不是在每个产生错误的地方打印日志
-
在程序顶部使用%+v把堆栈信息打印出来
func main(){ err := app.Run() if err != nil{ fmt.Printf("%+v\n",err) } }
Golang 1.1.3
Unwrap
go1.13为 errors 和 fmt 标准库包引入了新特性,以简化处理包含其他错误的错误。其中最重要的是: 包含另一个错误的 error 可以实现返回底层错误的 Unwrap 方法。如果 e1.Unwrap() 返回 e2,那么我们说 e1 包装 e2,您可以展开 e1 以获得 e2。按照此约定,我们可以为上面的 QueryError 类型指定一个 Unwrap 方法,该方法返回其包含的错误:
func (e *QueryError)Unwrap() error {
return e.Err
}
if errros.Is(err,ErrNotFound){
}
if errors.As(err,&e){
}
import (
"errors"
xerros "githuib.com/pkg/errors"
)
var errMy = errros.New("ddd")
func main(){
err := test2()
fmt.Println("main:%+v\n",err)
}
func test0() error {
return xerrors.Wrapf(errmy,"test0 failed")
}
func test1() error {
return test0()
}
func test2() error {
return test1()
}