go 错误处理策略

让我们演示一个文件复制的例子:函数需要打开两个文件,然后将其中一个文件的内容复制到另一个文件:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

上面的代码虽然能够工作,但是隐藏一个bug。如果第一个os.Open调用成功,但是第二个os.Create调用失败,那么会在没有释放src文件资源的情况下返回。虽然我们可以通过在第二个返回语句前添加src.Close()调用来修复这个BUG;但是当代码变得复杂时,类似的问题将很难被发现和修复。我们可以通过defer语句来确保每个被正常打开的文件都能被正常关闭:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

defer语句可以让我们在打开文件时马上思考如何关闭文件。不管函数如何返回,文件关闭语句始终会被执行。同时defer语句可以保证,即使io.Copy发生了异常,文件依然可以安全地关闭。

前文我们说到,Go语言中的导出函数一般不抛出异常,一个未受控的异常可以看作是程序的BUG。但是对于那些提供类似Web服务的框架而言;它们经常需要接入第三方的中间件。因为第三方的中间件是否存在BUG是否会抛出异常,Web框架本身是不能确定的。为了提高系统的稳定性,Web框架一般会通过recover来防御性地捕获所有处理流程中可能产生的异常,然后将异常转为普通的错误返回。

让我们以JSON解析器为例,说明recover的使用场景。考虑到JSON解析器的复杂性,即使某个语言解析器目前工作正常,也无法肯定它没有漏洞。因此,当某个异常出现时,我们不会选择让解析器崩溃,而是会将panic异常当作普通的解析错误,并附加额外信息提醒用户报告此错误。

func ParseJSON(input string) (s *Syntax, err error) {
    defer func() {
        if p := recover(); p != nil {
            err = fmt.Errorf("JSON: internal error: %v", p)
        }
    }()
    // ...parser...
}

标准库中的json包,在内部递归解析JSON数据的时候如果遇到错误,会通过抛出异常的方式来快速跳出深度嵌套的函数调用,然后由最外一级的接口通过recover捕获panic,然后返回相应的错误信息。

Go语言库的实现习惯: 即使在包内部使用了panic,但是在导出函数时会被转化为明确的错误值。



原文地址:https://www.cnblogs.com/ithubb/p/14187895.html