Go Error Handling Best Practices

The most obvious difference between Go's error and Java's Exception is:

  • Native libraries do not carry stacktrace
  • Native library does not support Wrap

This brings some troubles to program debug ging, so we will use github.com/pkg/errors to replace the native errors package to handle Error s.

However, due to the high probability of not using github.com/pkg/errors for errors in third-party libraries, inconsistent handling methods will cause trouble. A set of rules is defined below to unify:

  • The error of own new, including stacktrace according to the situation
  • Do not wrap the error returned by your own code
  • wrap the error returned by the third-party library
  • Try to use error only for exceptions

Explain in detail below.

The error of own new, including stacktrace according to the situation

If error is regarded as a return value, then stacktrace is not needed in this case, for example:

import "errors"

// close order
func closeOrder(id string) error {
    ...
    if order.IsPaid {
        return errors.New("Closing paid orders is not allowed")
    }
    ...
}
copy

If error is regarded as an abnormal result, then stacktrace must be carried:

import "github.com/pkg/errors"

// close order
func closeOrder(id string) error {
    ...
    if dbDown {
        // github.com/pkg/errors New function will carry stacktrace information
        return errors.New("database down")
    }
    ...
}
copy

Of course, whether this error is a return value or an abnormal result is entirely up to you.

Do not wrap the error returned by your own code

The purpose of wrap error is to add stacktrace to the error package.

And when you call the code you wrote, the called code itself has already decided whether to carry stacktrace (see the previous item), so there is no need to wrap it here.

// close order
func closeOrder(id string) error {
    ...
}

// Batch close orders
func batchCloseOrder(ids []string) error {
    for _, id := range ids {
        err := closeOrder(id)
        if err != nil {
            // There is no need to wrap, just throw it up
            return err
        }
    }
}
copy

wrap the error returned by the third-party library

The vast majority of third-party library code doesn't use github.com/pkg/errors, and they're basically designed to treat errors as exceptions. So in this case, you should wrap and then throw up:

import "github.com/pkg/errors"

_, err := db.ExecContext(ctx, query, ...)
if err != nil {
    // github.com/pkg/errors Wrap function wraps stacktrace
    return errors.Wrap(err, "update Order failed")
}
// If it is simpler, it can be like this
return errors.WithStack(err)
copy

Try to use error only for exceptions

Error handling and Go Mentioned in:

Go code uses error values to indicate an abnormal state

So try to use error only as an exception condition, not as a return value.

Print stacktrace of error

The error constructed by errors and the error returned by most third-party libraries do not carry stacktrace, so they cannot be printed:

import "errors"

fmt.Print(errors.New("abc"))
// result
// abc

fmt.Printf("%v", errors.New("abc"))
// result
// abc
copy

Errors constructed using github.com/pkg/error s require a special way to print out the stacktrace:

import "github.com/pkg/errors"

fmt.Print(errors.New("abc"))
// result
// abc

fmt.Printf("%v", errors.New("abc"))
// result
// abc

fmt.Printf("%+v", errors.New("abc"))
// result
// abc
// somefunc
// 	/path/to/some_func.go:<line>
// testing.tRunner
// 	/usr/local/opt/go/libexec/src/testing/testing.go:1259
// runtime.goexit
// 	/usr/local/opt/go/libexec/src/runtime/asm_amd64.s:1581
copy

You can see that the stacktrace is only printed when fmt format %+v is used.

Certain logging libraries, such as go.uber.org/zap meeting Automatic Identification error from github.com/pkg/errors, will print stacktrace (note errVerbose field):

import (
    "github.com/pkg/errors"
    "go.uber.org/zap"
)

logger.Errorw("errors.New", "err", errors.New("simple"))
// result
// {"level":"error","ts":"...","caller":"...","msg":"errors.New","err":"simple","errVerbose":"<stacktrace>"}

logger.Errorw("errors.New", zap.NamedError("err", errors.New("simple")))
// The result is the same as above
copy

Tags: Go git github Open Source

Posted by ediehl on Mon, 21 Nov 2022 06:02:43 +0300