避免检查错误是否为零重复?

Baj*_*aju 50 go

我正在学习go,我的一些代码看起来像这样:

a, err := doA()
if err != nil {
  return nil, err
}
b, err := doB(a)
if err != nil {
  return nil, err
}
c, err := doC(b)
if err != nil {
  return nil, err
}
... and so on ...
Run Code Online (Sandbox Code Playgroud)

这看起来有点不对,因为错误检查占用了大部分行.有没有更好的方法来进行错误处理?我可以通过一些重构来避免这种情况吗?

更新:谢谢你的所有答案.请注意,在我的示例中,doB取决于a,doC取决于b等.因此,大多数建议的重构在这种情况下不起作用.还有其他建议吗?

Gus*_*yer 40

这是一个常见的抱怨,有几个答案.

以下是一些常见的:

1 - 不是那么糟糕

这是对这些投诉的一种非常普遍的反应.你的代码中有一些额外的代码行实际上并不是那么糟糕.这只是一个便宜的打字,在阅读方面非常容易处理.

2 - 这实际上是件好事

这是基于这样一个事实,即键入和读取这些额外的行是一个非常好的提醒,事实上你的逻辑可能在那时逃脱,你必须撤消你在它前面的行中放置的任何资源管理.这通常与异常相比较,异常可以以隐式方式破坏逻辑流,迫使开发人员始终记住隐藏的错误路径.前段时间我在这里写了一篇更深入的咆哮.

3 - 使用恐慌/恢复

在某些特定情况下,您可以通过使用panic已知类型来避免某些工作,然后recover在您的包代码进入世界之前使用,将其转换为正确的错误并返回它.这种技术最常见于展开递归逻辑,例如(联合国)编组.

我个人努力不要滥用太多,因为我更紧密地与第1点和第2点相关联.

4 - 稍微重新组织代码

在某些情况下,您可以稍微重新组织逻辑以避免重复.

作为一个简单的例子,这个:

err := doA()
if err != nil {
    return err
}
err := doB()
if err != nil {
    return err
}
return nil
Run Code Online (Sandbox Code Playgroud)

也可以组织为:

err := doA()
if err != nil {
    return err
}
return doB()
Run Code Online (Sandbox Code Playgroud)

5 - 使用命名结果

有些人使用命名结果从return语句中去掉err变量.不过,我建议不要这样做,因为它节省的很少,降低了代码的清晰度,并且当在纾困退货声明之前定义一个或多个结果时,逻辑容易出现细微问题.

6 - 在if条件之前使用语句

正如Tom Wilde在下面的评论中提醒的那样,ifGo中的陈述在条件之前接受了一个简单的陈述.所以你可以这样做:

if err := doA(); err != nil {
    return err
}
Run Code Online (Sandbox Code Playgroud)

这是一个很好的Go成语,经常使用.

在某些特定的情况下,我宁愿避免以这种方式嵌入语句只是为了使其自身站立以达到清晰的目的,但这是一个微妙而个人的事情.

  • 1.这不是*行*行:他的大部分代码都是错误处理[75%].2:这不是一个提醒,因为你很容易忘记如果(错)和你的错误将被吞没.3:他不能使用恐慌,因为他不是doA()或file.create()等的作者...... 4:你不能两次使用":=".5:命名参数如何缩短代码?6:如果doA()还返回在检查错误后需要使用的结果,该怎么办? (13认同)
  • 你的第4点完全破坏了你的第1点和第2点.如果你速记它,你就会隐含地打破逻辑流,迫使开发人员想到doB(a)的可能结果. (3认同)
  • 在您的所有示例中,您从未将函数结果分配给任何变量。或者,准确地说,如果您遵循约定,您将其分配给名为 `err` 的变量。 (2认同)

nem*_*emo 14

2023年的答案

现在,您可以使用errors包中的帮助程序来errors.Join处理这些情况:

x, err1 := doSomething(2)
y, err2 := doSomething(3)

if err := errors.Join(err1, err2); err != nil {
    return err
}
Run Code Online (Sandbox Code Playgroud)

如果您想过滤要单独处理的特定错误(而不是简单地将它们记录下来并进行一般故障处理),您可以使用errors.Aserrors.Is

func foo() error {
    x, err1 := doSomething(2)
    y, err2 := doSomething(3)

    if err := errors.Join(err1, err2); err != nil {
        return err
    }
}

err := foo()


if error.Is(err, fs.ErrNotExist) {
    // handle fill not found by, e.g., printing
    // a special message but concrete error cannot be
    // accessed directly, see `.As` below for that
}

var notExistErr *fs.ErrNotExist
if error.As(err, notExistErr) {
    // only reached if assignment is possible
    // now `notExistErr` can be used to extract info about err
}

Run Code Online (Sandbox Code Playgroud)

2013年的回答

如果您有许多此类重复发生的情况,其中您有多个错误检查,您可以为自己定义一个实用程序函数,如下所示:

func validError(errs ...error) error {
    for i, _ := range errs {
        if errs[i] != nil {
            return errs[i]
        }
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

这使您能够选择错误之一,并在存在非零错误时返回。

使用示例(完整版本正在播放):

x, err1 := doSomething(2)
y, err2 := doSomething(3)

if e := validError(err1, err2); e != nil {
    return e
}
Run Code Online (Sandbox Code Playgroud)

当然,这仅适用于函数不相互依赖的情况,但这是总结错误处理的一般前提。


Nic*_*ood 7

您可以使用命名返回参数来缩短一些事情

游乐场链接

func doStuff() (result string, err error) {
    a, err := doA()
    if err != nil {
        return
    }
    b, err := doB(a)
    if err != nil {
        return
    }
    result, err = doC(b)
    if err != nil {
        return
    }
    return
}
Run Code Online (Sandbox Code Playgroud)

在你用Go编程之后,你会明白必须检查每个函数的错误,这会让你思考如果函数出错会导致它实际意味着什么,以及你应该如何处理它.

  • 虽然我 100% 同意检查错误是一件好事,但没有理由需要如此冗长。例如,Rust 强制您检查任何返回 Result 类型的函数的错误,但有一个 ? 运算符,它只是“如果有错误立即返回”的语法糖。您只有 1 行代码,而不是 4 行代码,并且您仍然必须显式处理每个错误。那些“if err != nil”块只是噪音。 (6认同)