Handling multiple errors in go

2019-01-25 08:55发布

I'm new to go and finding the error handling to be extremely verbose. I've read the reasoning for it and mostly agree, but there are a few places where it seems like there's more code to handle errors than actually do the work. Here is a (contrived) example, where I pipe "Hello world!" into cat and read and print the output. Basically every line has three more to handle the error, and I'm not really even handling anything.

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
}

Is there an idiomatic, clean way to handle this? I just feel like I'm missing something.

4条回答
虎瘦雄心在
2楼-- · 2019-01-25 09:27

For idiomatic, refer to peterSO's answer which begins to touch on the subject of returning errors, and this can be taken further by wrapping errors with some extra bit of information related to the context of the call within your application.

There may be cases where iterative runs over an operation might warrant something more generalized with some unusually creative examples in the following link, but as I commented on that question, it was a bad code example to examine: Go — handling multiple errors elegantly?

Regardless, looking solely at the example you have, this is nothing more than a one-off in main, so treat it like such if you're just looking to mess around as one might do in an interactive python console.

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))
}
查看更多
太酷不给撩
3楼-- · 2019-01-25 09:39

In a situation like this, I usually just flatten it out a bit.

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
}

Still ugly, but at least it's less vertical, and we get some alignment.

I can't say this adheres to any sort of convention, but it's a whole lot easier to read IMO.

查看更多
劳资没心,怎么记你
4楼-- · 2019-01-25 09:44

I just wrote a few hundreds lines in Go, so I'm not titled to indicate any idiomatic way. However, in case of repetitive call-and-check-error steps , I find that the code is a bit easier to write and to read as well if I revert the logic : instead of checking the condition for exiting (err != nil ), I check the condition for continuing ( err == nil ), like shown below.
This can be done, if you have an unique way to handle the error, no matter which error is, like returning to the caller or printing/logging it. The drawback to this approach is that you can't implicitly declare the variables with :=, because they would have the scope of the if block in which they are assigned.

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
}
查看更多
劳资没心,怎么记你
5楼-- · 2019-01-25 09:53

Clearly, we must handle any errors; we can't just ignore them.

For example, trying to make the example less artificial,

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)
}

Output:

Hello world!
Hello world!

Error Handling and Go

In Go, error handling is important. The language's design and conventions encourage you to explicitly check for errors where they occur (as distinct from the convention in other languages of throwing exceptions and sometimes catching them). In some cases this makes Go code verbose.

查看更多
登录 后发表回答