错误处理

在golang中错误有专门的一个包errors来处理,golang定义了error的接口,还允许自定义错误类型。

type error interface {
    Error() string
}

错误支持==!=操作。

定义简单错误

errors包中提供New方法来生成error(底层类型是error.errorString)。

err1 := errors.New("error1")
err2 := errors.New("error2")

func makeErr(a int) error {
    if a==1 {
        return err1
    }
    if a==2 {
        return err2
    }
    return nil
}

fmt.Println(err1 == makeErr(1))
fmt.Println(err1 == makeErr(2))

自定义错误

只有实现error接口,你就可以定义新的一种错误类型

type myError struct {
	s    string
	code int
}

func (m *myError) Error() string {
	return fmt.Sprintf("code:%d,msg:%s", m.code, m.s)
}

var err1 = &myError{"error1", 100}
var err2 = &myError{"error1", 100}

func makeErr(a int) error {
	if a == 1 {
		return err1
	}
	if a == 2 {
		return err2
	}
	return nil
}
fmt.Println(makeErr(1)) //code:100,msg:error1
fmt.Println(err1 == makeErr(1)) //true
fmt.Println(err1 == makeErr(2)) //true

错误处理技巧

go1.13之前,go的错误处理方式代码写起来相当繁琐。go1.13吸收了go社区一些优秀的错误处理方式(pkg/errors),彻底解决被人诟病的问题。本文主要介绍的错误处理方式是基于go1.13的。

go 1.13error包 增加的errors.Unwraperrors.Aserrors.Is三个方法。 同时 fmt 包增加 fmt.Errorf("%w", err)的方式来wrap一个错误。

我们通过代码来了解它们的用法。

package main

import (
	"fmt"
	"errors"
)
type Err struct {
	Code int
	Msg string
}
func (e *Err) Error() string  {
	return fmt.Sprintf("code : %d ,msg:%s",e.Code,e.Msg)
}
var A_ERR = &Err{-1,"error"}
func a()  error {
	return A_ERR
}

func b()  error {
	err := a()
	return fmt.Errorf("access denied: %w", err) //使用fmt.Errorf wrap 另一个错误
}

func main()  {
	err := b()
	er := errors.Unwrap(err)  //如果一个错误包含 Unwrap 方法则返回这个错误,如果没有则返回nil
	fmt.Println(er ==A_ERR )


	fmt.Println(errors.Is(err,A_ERR)) // 递归调用Unwrap判断是否包含 A_ERR
	var e = &Err{}
    fmt.Println( errors.As(err, &e))
    
	if errors.As(err, &e) {         // 递归调用Unwrap是否包含A_ERR,如果有这赋值给e
		fmt.Printf("code : %d ,msg:%s",e.Code,e.Msg)
	}
}

运行代码

$ go run main.go
true
true
true
code : -1 ,msg:error

错误为什么为要被wrap?

在一个函数A中错误发生的时候,我们会返回这个错误,函数B调用函数A拿到这个错,但是函数B不想做其他处理,它也返回错误,但是要打上自己的信息,说明这个错误经过了B函数,所以Wrap err就有了使用场景。

用了wrap后,错误是链状结构,我们用errors.Unwrap,逐级遍历err。还有我们有时候不一定会关心所有链条上的错误类型,我们只判断是否包含某种特点错误类型,所以 errors.Iserrors.As 方法就出现了。

带上函数调用栈信息

标准库的错误处理基本上能我们日常的开发需求,而且基本上能做到很优雅的错误处理。但是有时候我们还想带上更多信息,比如函数调用栈。我们使用第三方库pingcap errors来实现。

package main
import (
	"fmt"
	pkgerr "github.com/pingcap/errors"
)
type Err struct {
	Code int
	Msg string
}
func (e *Err) Error() string  {
	return fmt.Sprintf("code : %d ,msg:%s",e.Code,e.Msg)
}
var A_ERR = &Err{-1,"error"}

func stackfn1() error {
	return  pkgerr.WithStack(A_ERR)
}

func main()  {
	err := stackfn1()
	fmt.Printf("%+v",err) //这边使用 “%+v”
}
```要介绍的错误处理方式是基于go1.13的。
$ go run main.go
code : -1 ,msg:error
main.stackfn1
	/home/wida/gocode/goerrors-demo/main.go:18
main.main
	/home/wida/gocode/goerrors-demo/main.go:22
runtime.main
	/home/wida/go/src/runtime/proc.go:203
runtime.goexit
	/home/wida/go/src/runtime/asm_amd64.s:1357
Process finished with exit code 0

有了函数调用栈信息,我们可以更好的定位错误。