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

c90pui9n  于 2023-06-27  发布在  Go
关注(0)|答案(6)|浏览(122)

我现在正在学习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 ...

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

**更新:**谢谢你的回答。请注意,在我的例子中,doB依赖于a,doC依赖于b等等。因此,大多数建议的重构在这种情况下都不起作用。还有其他建议吗

dwbf0jvd

dwbf0jvd1#

这是一个常见的抱怨,有几个答案。
以下是一些常见的:

1 -没那么糟

这是对这些投诉的一种非常普遍的React。事实上,在代码中多几行代码并不是那么糟糕。这只是一个便宜的打字,非常容易处理时,在阅读方面。

2 -这其实是件好事

这是基于这样一个事实,即输入和阅读这些额外的行是一个非常好的提醒,事实上您的逻辑可能会在此时逃逸,并且您必须撤销在它之前的行中放置的任何资源管理。这通常与异常相比较,异常可以以隐式的方式中断逻辑流,迫使开发人员始终记住隐藏的错误路径。不久前,我写了一篇关于here的更深入的文章。

3 -使用panic/recover

在某些特定情况下,您可以通过使用已知类型的panic来避免一些工作,然后在您的包代码发布之前使用recover,将其转换为适当的错误并返回该错误。这种技术最常用于展开递归逻辑,如(取消)封送拆收器。
我个人尽量不滥用这一点,因为我与第1点和第2点的关系更密切。

4 -稍微整理一下代码

在某些情况下,您可以稍微重新组织逻辑以避免重复。
举一个简单的例子:

err := doA()
if err != nil {
    return err
}
err := doB()
if err != nil {
    return err
}
return nil

也可以组织为:

err := doA()
if err != nil {
    return err
}
return doB()

5 -使用命名结果

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

6 -使用if条件前的语句

正如Tom Wilde在下面的评论中所提醒的那样,if语句在Go语言的accept a simple statement中位于条件之前。所以你可以这样做:

if err := doA(); err != nil {
    return err
}

这是一个很好的Go习惯用法,经常使用。
在某些特定的情况下,我更倾向于避免以这种方式嵌入语句,只是为了使其独立于清晰的目的,但这是一个微妙和个人的事情。

daupos2t

daupos2t2#

您可以使用命名返回参数来缩短一些时间
Playground link

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
}

在你用Go编程一段时间后,你会意识到必须检查每个函数的错误,这会让你思考如果函数出错了到底意味着什么,以及你应该如何处理它。

ulydmbyx

ulydmbyx3#

2023年答案

现在你可以使用errors包中的helper来处理这些情况,比如errors.Join

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

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

如果你想过滤掉你想单独处理的特定错误(而不是简单地将它们记录下来并进行一般的错误处理),你可以使用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
}

2013年的答案

如果你有很多这样的重复发生的情况,你有几个这样的错误检查,你可以定义自己的效用函数如下:

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

这使您可以选择其中一个错误,如果有一个非空的错误,则返回。
示例用法(full version on play):

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

if e := validError(err1, err2); e != nil {
    return e
}

当然,这只能在函数不相互依赖的情况下应用,但这是总结错误处理的一般前提条件。

p8h8hvxi

p8h8hvxi4#

您可以创建具有结果值和错误的上下文类型。

type Type1 struct {
    a int
    b int
    c int

    err error
}

func (t *Type1) doA() {
    if t.err != nil {
        return
    }

    // do something
    if err := do(); err != nil {
        t.err = err
    }
}

func (t *Type1) doB() {
    if t.err != nil {
        return
    }

    // do something
    b, err := t.doWithA(a)
    if err != nil {
        t.err = err
        return
    }

    t.b = b
}

func (t *Type1) doC() {
    if t.err != nil {
        return
    }

    // do something
    c, err := do()
    if err != nil {
        t.err = err
        return
    }

    t.c = c
}

func main() {

    t := Type1{}
    t.doA()
    t.doB()
    t.doC()

    if t.err != nil {
        // handle error in t
    }

}
krugob8w

krugob8w5#

可以将错误作为函数参数传递

func doA() (A, error) {
...
}
func doB(a A, err error)  (B, error) {
...
} 

c, err := doB(doA())

我注意到“html/template”包中的一些方法可以做到这一点。

func Must(t *Template, err error) *Template {
    if err != nil {
        panic(err)
    }
    return t
}
e3bfsja2

e3bfsja26#

在您看来,这是错误的,可能是因为您习惯于不在调用站点处理错误。这对于go来说是非常惯用的,但如果您不习惯的话,它看起来就像很多样板文件。
不过,它确实有一些优势。
1.您必须考虑在生成错误的站点处理此错误的正确方法。
1.很容易阅读代码,以查看代码将中止并提前返回的每个点。
如果它真的有bug,你可以用for循环和匿名函数来创造,但这通常会变得复杂和难以阅读。

相关问题