在golang中处理逻辑错误与编程错误的惯用方法

Posted

技术标签:

【中文标题】在golang中处理逻辑错误与编程错误的惯用方法【英文标题】:Idiomatic way to handle logic error vs programming error in golang 【发布时间】:2018-11-18 15:22:40 【问题描述】:

我一直在使用 golang 来自动化一些部署过程,我不得不使用 exec 包来调用一些 bash 脚本。

我用了exec.Command("/home/rodrigo/my-deploy.sh").CombinedOutput(),看到了他的实现

func (c *Cmd) CombinedOutput() ([]byte, error) 
    if c.Stdout != nil 
        return nil, errors.New("exec: Stdout already set")
    
    if c.Stderr != nil 
        return nil, errors.New("exec: Stderr already set")
    
    var b bytes.Buffer
    c.Stdout = &b
    c.Stderr = &b
    err := c.Run()
    return b.Bytes(), err

我意识到您在使用 CombinedOutput() 时不能分配 c.Stdout,我认为这没关系,但它被告知 api 调用者的方式不正确。

CombinedOutput() 在你以不好的方式使用它时会返回一个错误,所以如果你要使用CombinedOutput() 那么你不应该分配c.Stderrc.Stdout 之前,如果你这样做,那么你将收到错误。

但是这个错误不是因为你的脚本抛出错误,而是因为你使用了错误的api,在这种情况下我相信你应该得到一个panic,因为不应该处理糟糕的api使用(我认为) .

我来自 Java World,例如,当您以错误的方式使用某些方法时,您会收到 RuntimeException

public void run(Job job) throws NotCompletedJob 
    if (job.getId() != null) 
        throw new IllegalArgumentException("This job should not have id");
    
    job.setId(calculateId());
    job.run();

有了这个签名,我可以知道我用一个有 id 的 Job 调用 run(obj); 是错误的,事实上我可以区分我的脚本是否有错误,或者我以错误的方式使用 api。

NotCompletedJob 是一个检查异常,所以我必须处理它,但IllegalArgumentException 不是,所以我可以随时得到它。捕获IllegalArgumentException 或任何其他RuntimeException 并不总是被认为是一种好的做法,因为从程序员的角度来看,它们表明您有一个错误,而这不是像NotCompletedJob 这样的预期错误。

话虽如此,在当前的CombinedOutput() 实现中,如何区分编程错误(例如错误的 api 用法)和预期错误(脚本未正常完成)?

为了澄清我的担忧,我并不是说当前的 CombinedOuput 实现是错误的,但我不明白调用者如何区分这是正在执行的命令的错误还是由他的错误引起的错误api使用。

我相信最好的方法是在调用者以错误的方式使用 api 时惊慌失措,就像调用者将 nil 引用传递给期望非 nil 引用的函数时一样(实际上这是当前行为)。

【问题讨论】:

也许应该是panic,但这是一个有争议的问题,因为在 go1 中无法更改 API。 @JimB 没错,但我相信这是否可以改变并不重要,但它一定是恐慌或错误。 因为函数在使用不正确时总是会返回错误,并且错误文本对问题的描述足以让程序员确定这是一个逻辑错误,因此无需以编程方式将逻辑错误与其他错误区分开来在这种情况下。 @rvillablanca 也许你的 Java 代码可以在 Golang 中转换to this(是的,据我所知,这是一种常见的模式) 询问“CombinedOutput() 的实现是否是最好的方法?”在这里没有用,因为它不是我们要改变的。 API 已设置,因此您可以通过测试来验证您是否正确使用它,或者通过匹配错误字符串来使用它。 【参考方案1】:

我来自Java World,当你错误地使用某些方法时 这样你就会收到一个 RuntimeException。


您现在处于 Go 世界中。因此,该论点是无效的。放弃 Java。


The Go Programming Language Specification

Handling panics

两个内置功能,恐慌和恢复,协助报告和 处理运行时恐慌和程序定义的错误情况。

func panic(interface) 
func recover() interface

在执行函数 F 时,显式调用 panic 或运行时 恐慌终止 F 的执行。任何由 F 延迟的函数都是 然后照常执行。接下来,由 F 的调用者运行的任何延迟函数 运行,依此类推,直到任何由***函数延迟 执行 goroutine。此时,程序终止,并且 报告错误情况,包括参数的值 恐慌。这种终止序列称为恐慌。


围棋博客

Defer, Panic, and Recover

Go 库中的约定是,即使包使用 内部恐慌,其外部 API 仍然显示显式错误 返回值。


Go Code Review Comments

本页收集了 Go 代码评审过程中常见的 cmets,所以 一个详细的解释可以用简写来引用。 这是一份常见错误的清单,而不是风格指南。

Don't Panic

见https://golang.org/doc/effective_go.html#errors。不要使用恐慌 用于正常的错误处理。使用错误和多个返回值。


Effective Go

Errors

向调用者报告错误的常用方法是将错误返回为 一个额外的返回值。


您的 Go 服务器程序同时处理 100,000 个客户端。如果发生错误,报告并处理;始终检查错误。不要使用panic 使所有 100,000 个客户端崩溃。 Go 包不应该panic

阅读 Go 文档和 Go 标准库代码。

【讨论】:

以上是关于在golang中处理逻辑错误与编程错误的惯用方法的主要内容,如果未能解决你的问题,请参考以下文章

惯用节点错误处理

如果成功,从没有结果的函数返回错误的惯用方法是啥?

惯用的 goroutine 终止和错误处理

Golang✔️走进 Go 语言✔️ 第十九课 错误处理 & 时间处理

Golang✔️走进 Go 语言✔️ 第十九课 错误处理 & 时间处理

Clojure 中的惯用错误处理