Error Handing with Golang
Go errors are values.
Naming:Error types end in "Error" and error variables start with "Err" or "err".
error:程序还可以继续运行,错误可以被修复或丢弃
error是个简单的内建接口
type error interface {
Error() string
}
自定义error范例
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string // "open", "unlink", etc.
Path string // The associated file.
Err error // Returned by the system call.
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
判断error类型可以用type switch或type assertion
for try := 0; try < 2; try++ {
file, err = os.Create(filename)
if err == nil {
return
}
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
deleteTempFiles() // Recover some space.
continue
}
return
}
标准库中error示例
// uintptr is an integer type that is large enough to hold the bit pattern of any pointer.
// type uintptr uintptr // 这个在builtin.go文件中定义的,可以看出它不是个指针类型而是用来存储内存地址的
// syscall_unix.go
type Errno uintptr
func (e Errno) Error() string {
if 0 <= int(e) && int(e) < len(errors) {
s := errors[e]
if s != "" {
return s
}
}
return "errno " + itoa(int(e))
}
func (e Errno) Is(target error) bool {
switch target {
case oserror.ErrPermission:
return e == EACCES || e == EPERM
case oserror.ErrExist:
return e == EEXIST || e == ENOTEMPTY
case oserror.ErrNotExist:
return e == ENOENT
}
return false
}
// zerrors_darwin_amd64.go
// Errors
const (
E2BIG = Errno(0x7)
EACCES = Errno(0xd)
EADDRINUSE = Errno(0x30)
......
)
// errors.go
var (
ErrInvalid = errors.New("invalid argument")
ErrPermission = errors.New("permission denied")
ErrExist = errors.New("file already exists")
ErrNotExist = errors.New("file does not exist")
ErrClosed = errors.New("file already closed")
)
panic:程序不能继续运行,错误不可忽视和修复
panic是个内建函数,panic后程序转而执行defer,继而终止
常用在init()函数,程序初始化失败时
recover:错误修复
前面说引发panic时,会执行defer stack,那么可以在defer stack中处理错误。如果程序不必要终止,则可以recover错误。当错误被recover住的时候,会停止执行stack中的后续defer
示例:One application of recover is to shut down a failing goroutine inside a server without killing the other executing goroutines.
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
当然如果在recover时,recover代码块内逻辑错误还是会导致新的错误,程序会re-panic
Wrap and Unwrap error:与error玩俄罗斯套娃游戏
Unwrap, Is, As 函数在wrap.go文件定义(该文件目前只有这三个函数)
errors.As(err Error, target interface{}) bool 函数是将err这个error`s chain(套娃)的最外层error,赋值给target。如果同类error则赋值成功,不是则失败。当然参数有问题会panic,而参数匹配问题需要好好理解。
准确的说,As函数会试着将err参数赋值给target参数所指的对象。于是如果target是nil那么会panic,而且是target所指的对象,即*target,要是个error类型或者是个interface{}
type errorOne string
func (err *errorOne) Error() string {
return string(*err)
}
type errorTwo struct {
two string
one error
}
func (err *errorTwo) Error() string {
return err.two + err.one.Error()
}
func main() {
one := errorOne("111")
two := errorTwo{two:"222", one:&one}
var what *errorTwo
// 注意:下面使用what不加&则会panic,因为what还是个nil指针
// 即使what不是个nil的errorTwo指针还是会panic,因为实现Error接口的是*errorTwo,不是errorTwo。
// 所以将个*errorTwo类型当作target,那么*target就是errorTwo;
// 因此要将**errorTwo当作target,这样*target才是*errorTwo
fmt.Println(errors.As(&two, &what))
}
errors.Is(err, target error) bool 函数会判断err这个error`s chain(套娃)是否有某层的error是target。
注意:只有实现了Unwrap方法的error才会出现在这个error`s chain里(套娃里),对于没实现Unwrap方法的error会使得这个chain断掉。最简单的方式是使用fmt.Errorf("%w", err)它会返回一个wrap好err的error
func main() {
one := errorOne("111")
two := errorTwo{two:"222", one:&one}
err := fmt.Errorf("%w", &two)
// flase 因为*errorTwo没有实现Unwrap方法
fmt.Println(errors.Is(err, &one))
errW1 := fmt.Errorf("wrap1-%w", &one)
errW2 := fmt.Errorf("wrap1-%w", errW1)
fmt.Println(errors.Is(errW2, &one)) // true
}
如果要想能够使用到Is方法的便捷,但是又不想使用fmt.Errorf+%w这个返回的“匿名”error,那么可以自己实现Unwrap方法。
准确来讲,也不能说fmt.Errorf返回的的error是匿名的,因为Errorf的源码实现里可以看到,返回的error要么是*wrapError类型,要么是errors.New出来的,而New返回的的是*errorString类型。只不过*wrapError和*errorString都是包外不可见的。
func Unwrap(err error) error:Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.
func (err *errorTwo) Unwrap() error {
return err.one
}
func main() {
one := errorOne("111")
two := errorTwo{two:"222", one:&one}
err := fmt.Errorf("%w", &two)
// 此时会返回true,因为*errorTwo实现了Unwrap方法
fmt.Println(errors.Is(err, &one))
}
Is的巨坑:Is判断时使用的是“==”,这就使得如果想要判断的target是使用指针接收者实现error接口的,那么只有当套娃中某个error和target完全是同一个instance时,即内存地址一样,完全就是一个东西时才返回true。
// 这是Is函数实现
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
for {
// 使用 == 判断,使得如果err和target是指针类型那就是判断内存地址了
if isComparable && err == target {
return true
}
// 揭开套娃(提取出包含Is函数的匿名接口实现),递归判断
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporing target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {
return false
}
}
}
type errorOne struct {
msg string
}
func (err *errorOne) Error() string {
return err.msg
}
type errorTwo struct {
two string
one error
}
func (err *errorTwo) Error() string {
return err.two + err.one.Error()
}
func main() {
errW0 := errorOne{msg: "111"}
errW1 := fmt.Errorf("wrap1-%w", &errW0)
errW2 := fmt.Errorf("wrap1-%w", errW1)
fmt.Println(errors.Is(errW2, &errorOne{"111"}))// false
fmt.Println(errors.Is(errW2, &errW0)) // true
fmt.Println(errW0 == errorOne{"111"}) // true
fmt.Println(&errW0 == &errorOne{"111"}) // false
}
可以通过自己重写Is函数的方式,自己实现 interface{ Is(error) bool } 这个匿名接口。
type errorOne struct {
msg string
}
func (err *errorOne) Error() string {
return err.msg
}
func (err *errorOne) Is(target error) bool {
if target == nil {
return err == target
}
switch target.(type) {
case *errorOne:
return err.Error() == target.Error()
default:
return false
}
}
func main() {
one := errorOne{"111"}
two := fmt.Errorf("222%w", &one)
fmt.Println(errors.Is(two, &one)) // true
// 下面两行输出,当有上面Is方法的时候都为true,当把上面Is方法注释掉时都为false
fmt.Println(errors.Is(two, &errorOne{"111"}))
fmt.Println(errors.Is(&one, &errorOne{"111"}))
}