Golang 错误处理

魔幻涂鸦

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"}))
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容