错误处理
在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.13
的error
包 增加的errors.Unwrap
,errors.As
,errors.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.Is
和errors.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
有了函数调用栈信息,我们可以更好的定位错误。