错误处理技术

我刚刚开始使用 Go,我的代码开始有很多这样的东西:

   if err != nil {
//handle err
}

或者这个

  if err := rows.Scan(&some_column); err != nil {
//handle err
}

是否有一些好的习惯用法/策略/最佳实践来检查和处理 Go 中的错误?

编辑澄清: 我没有抱怨或建议围棋团队拿出更好的东西。我想知道我做的是否正确,或者我是否错过了社区提出的一些技巧。谢谢大家。

24574 次浏览

Your code is idiomatic and in my opinion it is the best practice available. Some would disagree for sure, but I would argue that this is the style seen all over the standard libraries in Golang. In other words, Go authors write error handling in this way.

I would agree with jnml's answer that they are both idiomatic code, and add the following:

Your first example:

if err != nil {
//handle err
}

is more idiomatic when dealing with more than one return value. for example:

val, err := someFunc()
if err != nil {
//handle err
}
//do stuff with val

Your second example is nice shorthand when only dealing with the err value. This applies if the function only returns an error, or if you deliberately ignore the returned values other than the error. As an example, this is sometimes used with the Reader and Writer functions that return an int of the number of bytes written (sometimes unnecessary information) and an error:

if _, err := f.Read(file); err != nil {
//handle err
}
//do stuff with f

The second form is referred to as using an if initialization statement.

So with regards to best practices, as far as I know (except for using the "errors" package to create new errors when you need them) you've covered pretty much everything you need to know abut errors in Go!

EDIT: If you find you really can't live without exceptions, you can mimic them with ABC0,ABC1 & recover.

I made a library for streamlined error handling and piping through a queue of Go functions.

You can find it here: https://github.com/go-on/queue

It has a compact and a verbose syntactic variant. Here is an example for the short syntax:

import "github.com/go-on/queue/q"


func SaveUser(w http.ResponseWriter, rq *http.Request) {
u := &User{}
err := q.Q(
ioutil.ReadAll, rq.Body,  // read json (returns json and error)
)(
// q.V pipes the json from the previous function call
json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
)(
u.Validate,               // validate the user (returns error)
)(
u.Save,                   // save the user (returns error)
)(
ok, w,                    // send the "ok" message (returns no error)
).Run()


if err != nil {
switch err {
case *json.SyntaxError:
...
}
}
}

Please be aware that there is a little performance overhead, since it makes use of reflection.

Also this is not idiomatic go code, so you will want to use it in your own projects, or if your team agrees on using it.

Six months after this question was asked Rob Pike wrote a blog post titled Errors are Values.

In there he argues that you don't need to program in the way presented by the OP, and mentions several places in the standard library where they use a different pattern.

Of course a common statement involving an error value is to test whether it is nil, but there are countless other things one can do with an error value, and application of some of those other things can make your program better, eliminating much of the boilerplate that arises if every error is checked with a rote if statement.

...

Use the language to simplify your error handling.

But remember: Whatever you do, always check your errors!

It's a good read.

You can clean up your error handling code for similar errors (since errors are values you have to be careful here) and write a function which you call with the error passed in to handle the error. You won't have to write "if err!=nil {}" every time then. Again, this will only result in cleaning up the code, but I don't think it is the idiomatic way of doing things.

Again, just because you can doesn't mean you should.

If you want precise control of errors, this may not be the solution, but for me, most of the time, any error is a show stopper.

So, I use functions instead.

func Err(err error) {
if err!=nil {
fmt.Println("Oops", err)
os.Exit(1)
}
}


fi, err := os.Open("mmm.txt")
Err(err)

goerr allows to handle errors with functions

package main


import "github.com/goerr/goerr"
import "fmt"


func ok(err error) {
if err != nil {
goerr.Return(err)
// returns the error from do_somethingN() to main()
// sequence() is terminated
}
}


func sequence() error {
ok(do_something1())
ok(do_something2())
ok(do_something3())


return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
fmt.Println("DOING 3")
return nil
}


func main() {
err_do_something := goerr.OR1(sequence)


// handle errors


fmt.Println(err_do_something)
}

A "strategy" for handling errors in golang and in other languages is to continuously propogate errors up the call stack until you are high enough in the call stack to handle that error. If you tried handling that error too early, then you will likely end up repeating code. If you handle it too late, then you will break something in your code. Golang makes this process super easy as it makes it super clear whether you are handling an error at a given location or propagating it up.

If you are going to be ignoring the error, a simple _ will reveal this fact very clearly. If you are handling it, then exactly which case of the error you are handling is clear as you will check for it in the if statement.

Like people said above, an error is actually just a normal value. This treats it as such.

Most in industry, are following standard rules mentioned in golang documentation Error handling and Go. And it also helps doc generation for project.

The Go gods have published a "draft design" for error handling in Go 2. It aims to change the errors idiom:

Overview and Design

They want feedback from users!

Feedback wiki

Briefly, it looks like:

func f() error {
handle err { fmt.Println(err); return err }
check mayFail()
check canFail()
}

UPDATE: The draft design has received a lot of criticism, so I drafted Requirements to Consider for Go 2 Error Handling with a menu of possibilities for an eventual solution.

Below is my take on reducing error handling on for Go, sample is for when getting HTTP URL parameters :

(Design pattern derived from https://blog.golang.org/errors-are-values)

type HTTPAdapter struct {
Error *common.AppError
}


func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
requestUUID := uuid.Parse(mux.Vars(r)[param])
if requestUUID == nil {
adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
possibleError, http.StatusBadRequest)
}
return requestUUID
}

calling it for multiple possible parameters would be as below:

    adapter := &httphelper.HTTPAdapter{}
viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
if adapter.Error != nil {
return nil, adapter.Error
}

This is not a silver bullet, the downside is if you had multiple errors you are only able to get the last error.

But in this instance it is relatively repetitive and low risk, hence I can just get the last possible error.