Go try 新提案靠谱吗?想简化错误处理了

大家好,我是煎鱼。

在近日新的 try 提案《proposal: Go 2: error handling: try statement with handler》在社区引发了热议。

今天煎鱼和大家一起打开来看看,这能把 Go 错误处理机制给掀开重整不。

背景

来自 PingCAP 的提案作者 @Greg Weber 会干这事基于两个因素,一个是在《Go Developer Survey 2022 Q2 Results》中明确提到。

随着 Go1.18 泛型的发布,原先矛盾最深的泛型已经得到一个初步的解决方案。在社区调研上,开发者在使用 Go 时面临的最大挑战已经转移到了错误处理上,需要投入精力去 “解决” 它。

另外一个因素就是众所皆知的,Go 错误处理代码比较繁琐,常被工程师们戏称一个 Go 工程里有 30% 都 if err = nil。

如下代码:

_, err := f()
if err != nil {
    ...
}
_, err = r()
if err != nil {
    ...
}
_, err = w()
if err != nil {
    ...
}

希望让其更优雅。也有许多小伙伴认同这个设计,确实是简单、直观的处理,在社区形成了角力。

try-handler 提案

本次提案中所提到的解决方案,是增加一个新语句 try ,以此达到简洁的错误处理的作用,让 if err != nil 的处理丝滑起来。

如下代码:

try err, handler

编译器翻译后生成的代码:

if err != nil {
    return handler(err)
}

在函数中可以如下:

func(args...) (rtype1, rtypes..., rtypeN, error) {
    try err, handler
    ...
}

翻译后生成的代码:

func(args...) (rtype1, rtypes..., rtypeN, error) {
    if err != nil {
            return Zero(rtype1), Zeros(rtypes...)..., Zero(rtypeN), handler(err)
    }
    ...
}

也可以只针对 if err != nil 处理。如下代码:

try err

翻译后生成的代码:

if err != nil {
    return err
}

不会调用不存在的 handler 进行处理,将会直接返回。三行(if err != nil 的逻辑)直接变 3 个单词(try)。

不想写函数,也可以直接:

x, err := f()
try err, fmt.Errorf("f fail: %w", err)

针对 defer+try 的场景可以如下:

func CopyFile(src, dst string) error {
    defer try func(err error) error {
        return fmt.Errorf("copy %s %s: %w", src, dst, err)
    }
    ...
}

入参是比较灵活的,作者希望它是泛型,这样能够适配各种场景的要求。

示例和实践

针对本提案,原作者给出了各类使用场景的示例。如下代码:

import (
    "fmt"
)

// This helper should be defined in the fmt package
func Handlew(format string, args ...any) func(error) error {
    return func(err error) error {
        args = append(args, err)
        return fmt.Errorf(format+": %w", args...)
    }
}

// This helper should be defined in the fmt package
func Handlef(format string, args ...any) func(error) error {
    return func(err error) error {
        args = append(args, err)
        return fmt.Errorf(format+": %v", args...)
    }
}

func valAndError() (int, error) {
    return 1, fmt.Errorf("make error")
}

func newGo() (int, error) {
    x, err := valAndError()
    try err

    // Common formatting functions will already be provided
    i := 2
    x, err = valAndError()
    try err, Handlew("custom Error %d", i)

    // Using a custom error type
    // For convenience the error type can expose a method to set the error
    x, err = valAndError()
    try err, TheErrorAsHandler(i)
}

type TheError struct{
    num int
    err error
}

func (t TheError) Error() String {
    return fmt.Sprintf("theError %d %v", t.num, t.err)
}

func TheErrorAsHandler(num int) func(err) TheError {
    return func(err error) TheError {
        return theError{ num: i, err: err }
    }
}

另外在日常的 Go 工程中,提案作者认为 CopyFile 函数是新提案语句的一种很好的实践。为此基于 try-handler 进行了一版改造和说明。

如下代码:

// This helper can be used with defer
func handle(err *error, handler func(err error) error) {
    if err == nil {
        return nil
    }
    *err = handler(err)
}

func CopyFile(src, dst string) (err error) {
    defer handle(&err, func(err error) error {
        return fmt.Errorf("copy %s %s: %w", src, dst, err)
    })

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

    w, err := os.Create(dst)
    try err, func(err error) error {
            os.Remove(dst) // only if Create fails
            return fmt.Errorf("dir %s: %w", dst, err)
    }
    defer w.Close()

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

引入 try-hanlder 后,能够做到:

  • 插入错误的返回语句,进行机制预设。
  • 在返回错误之前将错误处理函数组合在一起,便于后续的处理。

总结

在这个新提案中,一旦实施,就可以减少如下代码的编写:

if err != nil {
  return ...
}

在代码编写上会节省一些行数,且可以为错误处理机制引入一些新的 ”操作“,这是该提案的优势。

但是从 Go 开发者的角度而言,会引入一些新的副作用,例如:初学者的学习成本、Go 工具链的改造、程序理解的复杂度增加。

另外新的语句,似乎比较难与 Go1.13 引入的 error.Is 和 As 有较好的相关联性。如果是做一个第三方用户库引入倒可以,但若是作为标准进入 Go 源代码中,似乎又有些格格不入(提案作者希望进入)。

看了那么多提案,Go 错误处理机制的 ”升级“,似乎陷入了手心手背都是肉的阶段...

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

推荐阅读

Go 图书系列


煎鱼的清汤锅
今天写代码了吗 :-) 博客地址:[链接]
8.2k 声望
12.7k 粉丝
0 条评论
推荐阅读
Go1.20 那些事:PGO、编译速度、错误处理等新特性,你知道多少?
最近 Go1.20 在 2 月 1 号已经正式发布,来的挺早的,以往都是拖到月底的,我翻了好几篇,发现最后还是做了不少功能取舍,放了些新特性(例如:arena 等)的鸽子!

煎鱼1

从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木150阅读 12.3k评论 10

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木66阅读 6.2k评论 16

如何使用 PHPStorm 进行优雅的项目开发?
PHP Storm 这个开发工具,很多 phper 应该有所耳闻,甚至也有不少人使用其作为生产工具,但是很多人都没有最大限度的使用它,本文就来总结一些优雅开发的小技巧。

唯一丶45阅读 4.8k评论 7

从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木44阅读 7.4k评论 6

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木33阅读 6.2k评论 9

从零搭建 Node.js 企业级 Web 服务器(五):数据库访问
回顾 从零搭建 Node.js 企业级 Web 服务器(一):接口与分层,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,控制层与服务层实现了业务处理过程,模型层定义了业务实体并以 对象-关系...

乌柏木34阅读 4.6k评论 9

8.2k 声望
12.7k 粉丝
宣传栏