Golang中错误处理策略演进

错误处理策略

  • 隐藏内部细节直接返回错误:上层不清楚错误是在哪一层导致的,只是知道了错误
  • 返回和检查错误值: 通过返回特定值表示是否成功或具体的错误,有点类似linux中的错误码处理
  • 自定义错误类型:逻辑中增加类型断言,断言成功就是具体的错误

隐藏内部细节直接返回错误

conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
    return err
}

返回和检查错误值

fp, err := os.Open("./xxx.txt")  
if err != nil {
    fmt.Println("打开文件失败。", err)
    return
}
defer fp.Close()
for {
    n, err2 := fp.Read(buf)
    if err2 == io.EOF {  // io.EOF表示文件末尾
        fmt.Println("文件读取结束")
        break
    }
    fmt.Print(string(buf[:n]))
}

自定义错误类型

type MyError struct {
    error errors.error
    Message string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("message:%s error=%s", e.Message, errors.Error())
}

func test() *MyError{
    return &MyError{Message:"xxx", error:errors.New("error")}
}
func main() {
    err := test()
    if _,ok := err.(*MyError);ok {
        //错误处理
    }
}

比较出名的errors包

因为golang errors比较“简洁”,所以这个时候出现了一些解决痛点的包
比较出名的是
https://github.com/pkg/errors

可以输出具体的堆栈

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

func test() error {
    cause := errors.New("test")
    return errors.WithStack(cause)
}

func main() {
    err := test()
    fmt.Printf("%+v", err)
}

输出

test
main.test
    /Users/timi/go/src/test/test.go:1485
main.main
    /Users/timi/go/src/test/test.go:1490
runtime.main
    /usr/local/go/src/runtime/proc.go:203
runtime.goexit
    /usr/local/go/src/runtime/asm_amd64.s:1357
main.test
    /Users/timi/go/src/test/test.go:1486
main.main
    /Users/timi/go/src/test/test.go:1490
runtime.main
    /usr/local/go/src/runtime/proc.go:203
runtime.goexit
    /usr/local/go/src/runtime/asm_amd64.s:1357%

多层嵌套错误


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

func error1() error {
    return errors.New("error1")
}

func inner() error {
    err := error1()
    if err != nil {
        return errors.Wrap(err, "inner")
    }
    return nil
}

func middle() error {
    err := inner()
    if err != nil {
        return errors.Wrap(err, "middle")
    }
    return nil
}
func outer() error {
    err := middle()
    if err != nil {
        return errors.Wrap(err, "outer")
    }
    return nil
}

func main() {
    err := outer()
    fmt.Printf("%+v\n", err)
    `fmt.Println(errors.Cause(err))

}

输出

error1
main.error1
    /Users/timi/go/src/test/test.go:1485
main.inner
    /Users/timi/go/src/test/test.go:1489
main.middle
    /Users/timi/go/src/test/test.go:1497
main.outer
    /Users/timi/go/src/test/test.go:1504
main.main
    /Users/timi/go/src/test/test.go:1512
runtime.main
    /usr/local/go/src/runtime/proc.go:203
runtime.goexit
    /usr/local/go/src/runtime/asm_amd64.s:1357
inner
main.inner
    /Users/timi/go/src/test/test.go:1491
main.middle
    /Users/timi/go/src/test/test.go:1497
main.outer
    /Users/timi/go/src/test/test.go:1504
main.main
    /Users/timi/go/src/test/test.go:1512
runtime.main
    /usr/local/go/src/runtime/proc.go:203
runtime.goexit
    /usr/local/go/src/runtime/asm_amd64.s:1357
middle
main.middle
    /Users/timi/go/src/test/test.go:1499
main.outer
    /Users/timi/go/src/test/test.go:1504
main.main
    /Users/timi/go/src/test/test.go:1512
runtime.main
    /usr/local/go/src/runtime/proc.go:203
runtime.goexit
    /usr/local/go/src/runtime/asm_amd64.s:1357
outer
main.outer
    /Users/timi/go/src/test/test.go:1506
main.main
    /Users/timi/go/src/test/test.go:1512
runtime.main
    /usr/local/go/src/runtime/proc.go:203
runtime.goexit
    /usr/local/go/src/runtime/asm_amd64.s:1357
++++++++
outer: middle: inner: error1

golang1.13 官方标准包errors新特性

主要增加了几个函数
fmt.Errorf//包装 每次调用包一次
errors.Unwrap//解包装 每次调用解一次
errors.Is
errors.As
单讲语法没什么意义
主要将以前写法和现在写法的区别
这里有个需要注意的点,就是1.13开始支持错误链,类似上面实现的那样 error1->inner->middle->outer

//错误链演示
import (
    "errors"
    "fmt"
)

var IamError error = errors.New("i am error1")

func error1() error {
    return IamError
}

func inner() error {
    err := error1()
    if err != nil {
        return fmt.Errorf("inner,%w", err)
    }
    return nil
}

func middle() error {
    err := inner()
    if err != nil {
        return fmt.Errorf("middle,%w", err)
    }
    return nil
}
func outer() error {
    err := middle()
    if err != nil {
        return fmt.Errorf("outer,%w", err)
    }
    return nil
}

func main() {
    err := outer()

    fmt.Println(err)
    fmt.Println("++++++++")
    fmt.Println(errors.Is(err, IamError))

}

输出

outer,middle,inner,i am error1
++++++++
true
if err == io.EOF//以前这么写判断
if errors.Is(err, io.EOF)//1.13可以这样写 只要错误链含有io.EOF就返回true
if _,ok := err.(*MyError);ok {//以前这么写判断

var myError *MyError//1.13可以这样写
errors.As(newErr, &myError)//1.13可以这样写 只要错误链含有myError就返回true
添加新评论