Go2错误处理提案:try还是check?

Go2错误处理提案:try还是check?


了解Go语言2.0的发展方向,请关注“光谷码农”公众号,我们带你了解最前沿的Go语言错误处理改进方向。


Go语言之父之一Robert Griesemer于2019-06-05新提交了一个try内置函数的提案,链接在这里:https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

Go2错误处理提案:try还是check?

Go错误处理的问题

Go语言的错误处理是大家吐槽比较多的地方,比如这种:

func CopyFile(src, dst string) error {
    r, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    defer r.Close()
    w, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    if _, err := io.Copy(w, r); err != nil {
        w.Close()
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    if err := w.Close(); err != nil {
        os.Remove(dst)
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    return nil
}

这里有太多重复的if err != nil错误处理代码。

if err != nil {
    return fmt.Errorf("copy %s %s: %v", src, dst, err)
}

错误处理不仅仅是繁琐,它打断了表达式流程,⽆法链式操作:

v0, err := strconv.Atoi("123")
if err != nil {
    return err
}

v1 := v0*2

在范型编程之后,错误处理是第二个被重点关注的改进方向。

曾经的提案:check & handle

在2018年,Go语言官方团队已经提交过一个错误改进提案:

func CopyFile(src, dst string) error {
    handle err {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    r := check os.Open(src)
    defer r.Close()
    w := check os.Create(dst)
    handle err {
        w.Close()
        os.Remove(dst) // (only if a check fails)
    }
    check io.Copy(w, r)
    check w.Close()
    return nil
}

通过check & handle关键字来简化错误处理流程。

check关键字可以吃掉一个多余的err,这样可以进行链式操作:

func printSum(a, b string) error {
    handle err { return err }
    fmt.Println("result:", check strconv.Atoi(x) + check strconv.Atoi(y))
    return nil
}

但是前一个提案的最大风险是增加了新的关键词。Go语言目前有25个关键字,突然增加到27个,影响和谐社会。

因此,新的try内置函数的提案,主要是为了减少Go语言语法的变化,通过内置的特权函数来解决错误处理的问题。

try内置函数

先看看新提案的try函数签名:

func try(t1 T1, t1 T2, … tn Tn, te error) (T1, T2, … Tn)

输入的是n个参数加一个err返回值,返回的结果和前check关键字提案类似:检测err后返回前n个参数。这个特权函数是一个泛型函数。

有了try内置函数之后,之前的CopyFile函数可以这样写:

func CopyFile(src, dst string) (err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("copy %s %s: %v", src, dst, err)
        }
    }()

    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst) // only if a “try” fails
        }
    }()

    try(io.Copy(w, r))
    try(w.Close())
    return nil
}

当try失败的时候,返回值中除了最后的err,其它的方针都是零值。需要注意的是,try函数无法处理复杂的错误(这个地方功能比之前的handle提案要弱一点)。

try函数也可以作为表达式使用(和check提案类似):

func printSum(a, b string) error {
        x := try(strconv.Atoi(a))
        y := try(strconv.Atoi(b))
        fmt.Println("result:", x + y)
        return nil
}

另外,在单元测试中也可以使用try函数,它会被重写为t.Fatal(err)语句。

特殊的错误

对于特殊的错误类型,try函数的提案还在考虑是否可以传入额外的处理函数。类似这样:

f := try(os.Open(filename),
    func(err error) error {
        return err
    },
)

这样的话,可以对返回的错误,以及上下文状态进行补救处理。

比如CopyFile例子中,失败时可以这样删除临时文件:

func CopyFile(src, dst string) (err error) {
    ...

    w := try(os.Create(dst), func(err error) error)
        w.Close()
        os.Remove(dst) // only if a “try” fails
        return err
    )
    defer w.Close()

    ...
}

当然,这样的设计也会带来新的问题:try函数的参数具体是什么形式?

补充

最后,try是一个特殊的函数(类似的还有init函数等),不要把它当作普通函数使用,不然容易掉坑里去。


Go2错误处理提案:try还是check?

原创文章,作者:酷毙编辑,如若转载,请注明出处:https://www.dailybtc.cn/go2%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e6%8f%90%e6%a1%88%ef%bc%9atry%e8%bf%98%e6%98%afcheck%ef%bc%9f/

发表评论

电子邮件地址不会被公开。 必填项已用*标注

联系我们

在线咨询:点击这里给我发消息

邮件:[email protected]

工作时间:周一至周五,9:30-18:30,节假日休息

QR code