处理Go语言中的多个错误

13

我是 Go 语言的新手,发现错误处理非常冗长。我已经了解了其原因,大多数情况下都同意,但有些地方似乎需要编写更多的代码来处理错误,而实际工作却很少。这里是一个(人为制造的)例子,我将“Hello world!”输入到 cat 中,并读取并打印输出。基本上每一行都要添加三行代码来处理错误,但实际上我还没有真正处理任何错误。

package main

import "fmt"
import "io"
import "io/ioutil"
import "os/exec"


func main() {
    cmd := exec.Command("cat", "-")
    stdin, err := cmd.StdinPipe()
    if err != nil {
        return
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return
    }
    err = cmd.Start()
    if err != nil {
        return
    }
    _, err = io.WriteString(stdin, "Hello world!")
    if err != nil {
        return
    }
    err = stdin.Close();
    if err != nil {
        return
    }
    output, err := ioutil.ReadAll(stdout)
    if err != nil {
        return
    }
    fmt.Println(string(output))
    return
}

有没有一种惯用的、简洁的方法来处理这个问题?我感觉自己好像漏掉了什么。


1
https://dev59.com/rWUp5IYBdhLWcg3wRF6c?rq=1 - user2303335
4个回答

7
显然,我们必须处理任何错误;不能忽略它们。
例如,为了使示例不那么人为,
package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "os/exec"
)

func piping(input string) (string, error) {
    cmd := exec.Command("cat", "-")
    stdin, err := cmd.StdinPipe()
    if err != nil {
        return "", err
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return "", err
    }
    err = cmd.Start()
    if err != nil {
        return "", err
    }
    _, err = io.WriteString(stdin, input)
    if err != nil {
        return "", err
    }
    err = stdin.Close()
    if err != nil {
        return "", err
    }
    all, err := ioutil.ReadAll(stdout)
    output := string(all)
    if err != nil {
        return output, err
    }
    return output, nil
}

func main() {
    in := "Hello world!"
    fmt.Println(in)
    out, err := piping(in)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    fmt.Println(out)
}

输出:

Hello world!
Hello world!

错误处理和Go语言

在Go语言中,错误处理很重要。这种语言的设计和约定鼓励你在发生错误时显式地检查它们(与其他语言抛出异常并有时捕获它们的惯例不同)。某些情况下,这会使Go代码变得冗长。


3
这并没有解决大量重复代码的问题。请参考用户2303335提供的链接以获取更好的解决方案。 - amon
4
@amon: 链接问题的答案都不尽如人意。阅读Go核心库的示例源代码,按设计要求,Go代码会检查错误。请自己重写问题中的代码,并展示你的成果。 - peterSO

0

关于惯用语,请参考peterSO的答案,他开始涉及返回错误的主题,这可以通过在应用程序中调用的上下文相关的一些额外信息来包装错误来进一步处理。

可能有些情况下,对操作进行迭代运行可能需要更通用的东西,以下链接中有一些异常创意的例子,但正如我在那个问题上评论的那样,这是一个糟糕的代码示例:Go — handling multiple errors elegantly?

无论如何,仅看你所拥有的示例,这只是一个在主函数中的一次性操作,因此,如果你只是想在交互式Python控制台中玩耍,就像这样对待它。

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "os/exec"
)

func main() {
    cmd := exec.Command("cat", "-")
    stdin, _ := cmd.StdinPipe()
    stdout, _ := cmd.StdoutPipe()

    cmd.Start()
    io.WriteString(stdin, "Hello world!")

    stdin.Close();
    output, _ := ioutil.ReadAll(stdout)

    fmt.Println(string(output))
}

2
我的示例只是为了简洁起见而使用了一次性的主函数。在我的实际代码中,我不能简单地忽略错误。 - David Stalnaker
简洁明了的代码可以完全改变答案。如果代码确实注定要成为主要代码,那么在遇到错误时使用log.Fatal是合适的。如果这是RPC调用的一部分,则包装错误并附加其他有用的信息(如果我们失败了?我们应该提供什么?)是合适的。我的评论重点是:返回错误,并且可以通过将错误与与应用程序中调用上下文相关的一些额外信息进行包装来进一步处理。忽略errs的代码示例是一个事后的想法和提醒,人们可以这样做。 - dskinner

0
我刚用Go写了几百行代码,所以我没有资格指出任何惯用的方式。 然而,在重复的调用和检查错误步骤的情况下,我发现如果我反转逻辑:不是检查退出条件(err != nil),而是检查继续条件(err == nil),那么代码会更容易编写和阅读,如下所示。
如果您有一种独特的处理错误的方法,无论是返回给调用者还是打印/记录它,都可以这样做。 这种方法的缺点是,您不能隐式地使用:=声明变量,因为它们将具有分配它们的if块的作用域。
func main() {
    var output []byte
    var stdin io.WriteCloser 
    var stdout io.Reader 

    cmd := exec.Command("cat", "-")

    stdin, err := cmd.StdinPipe()

    if err == nil {
      stdout, err = cmd.StdoutPipe()
    }

    if err == nil {
      err = cmd.Start()
    }

    if err == nil {
      _, err = io.WriteString(stdin, "Hello world!")
    }

    if err == nil {
    output, err = ioutil.ReadAll(stdout)
    } 

    if err == nil {
     err = stdin.Close();
    }

    if err == nil {
            fmt.Println(string(output))
    } else {
         fmt.Println(string(err.Error())) // Error handling goes here
    }

    return
}

-2
在这种情况下,我通常会将其稍微简化一下。
func myFunc() (err error) {
    cmd := exec.Command("cat", "-")

    stdin,  err := cmd.StdinPipe();                  if err != nil { return }
    stdout, err := cmd.StdoutPipe();                 if err != nil { return }

       err  = cmd.Start();                           if err != nil { return }
    _, err  = io.WriteString(stdin, "Hello world!"); if err != nil { return }
       err  = stdin.Close();                         if err != nil { return }
    o, err := ioutil.ReadAll(stdout);                if err != nil { return }

    fmt.Println(string(o))
    return
}

还是有点丑,但至少不再这么纵向了,我们也获得了一些对齐。

我不能说这遵循任何惯例,但在我看来它更容易阅读了。


14
第一次对你的代码应用 gofmt 将会破坏你的格式。 - peterSO

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接