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

随着 Go1.18 泛型的发布,原先矛盾最深的泛型已经得到一个初步的解决方案。在社区调研上,开发者在使用 Go 时面临的最大挑战已经转移到了错误处理上,需要投入精力去 “解决” 它。
首页 新闻资讯 行业资讯 Go try 新提案靠谱吗?想简化错误处理了

大家好,我是煎鱼。

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

背景

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

图片

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

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

如下代码:

复制

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

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

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

try-handler 提案

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

如下代码:

复制

try err, handler
  • 1.

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

复制

if err != nil {return handler(err)}
  • 1.

  • 2.

  • 3.

在函数中可以如下:

复制

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

  • 2.

  • 3.

  • 4.

翻译后生成的代码:

复制

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

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

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

复制

try err
  • 1.

翻译后生成的代码:

复制

if err != nil {return err}
  • 1.

  • 2.

  • 3.

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

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

复制

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

  • 2.

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

复制

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

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

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

示例和实践

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

复制

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 := 2x, 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 interr 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 }}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

  • 35.

  • 36.

  • 37.

  • 38.

  • 39.

  • 40.

  • 41.

  • 42.

  • 43.

  • 44.

  • 45.

  • 46.

  • 47.

  • 48.

  • 49.

  • 50.

  • 51.

  • 52.

  • 53.

另外在日常的 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}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

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

  • 插入错误的返回语句,进行机制预设。

  • 在返回错误之前将错误处理函数组合在一起,便于后续的处理。

总结

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

复制

if err != nil {
  return ...}
  • 1.

  • 2.

  • 3.

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

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

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

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


28    2022-10-24 08:55:13    Go 工具链 开发者