跳到正文
awesomego.org

Errors

Returning errors

Use error to signal that a function can fail. By convention, error is the last result parameter.

// Good:
func Good() error { /* ... */ }

Returning a nil error is the idiomatic way to signal a successful operation that could otherwise fail. If a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented otherwise. Commonly, the non-error return values are their zero values, but this cannot be assumed.

// Good:
func GoodLookup() (*Result, error) {
    // ...
    if err != nil {
        return nil, err
    }
    return res, nil
}

Exported functions that return errors should return them using the error type. Concrete error types are susceptible to subtle bugs: a concrete nil pointer can get wrapped into an interface and thus become a non-nil value (see the Go FAQ entry on the topic).

// Bad:
func Bad() *os.PathError { /*...*/ }

Tip: A function that takes a [context.Context] argument should usually return an error so that the caller can determine if the context was cancelled while the function was running.

Error strings

Error strings should not be capitalized (unless beginning with an exported name, a proper noun or an acronym) and should not end with punctuation. This is because error strings usually appear within other context before being printed to the user.

// Bad:
err := fmt.Errorf("Something bad happened.")
// Good:
err := fmt.Errorf("something bad happened")

On the other hand, the style for the full displayed message (logging, test failure, API response, or other UI) depends, but should typically be capitalized.

// Good:
log.Infof("Operation aborted: %v", err)
log.Errorf("Operation aborted: %v", err)
t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)

Handle errors

Code that encounters an error should make a deliberate choice about how to handle it. It is not usually appropriate to discard errors using _ variables. If a function returns an error, do one of the following:

  • Handle and address the error immediately.
  • Return the error to the caller.
  • In exceptional situations, call [log.Fatal] or (if absolutely necessary) panic.

Note: log.Fatalf is not the standard library log. See [#logging].

In the rare circumstance where it is appropriate to ignore or discard an error (for example a call to (*bytes.Buffer).Write that is documented to never fail), an accompanying comment should explain why this is safe.

// Good:
var b *bytes.Buffer

n, _ := b.Write(p) // never returns a non-nil error

For more discussion and examples of error handling, see Effective Go and best practices.

In-band errors

In C and similar languages, it is common for functions to return values like -1, null, or the empty string to signal errors or missing results. This is known as in-band error handling.

// Bad:
// Lookup returns the value for key or -1 if there is no mapping for key.
func Lookup(key string) int

Failing to check for an in-band error value can lead to bugs and can attribute errors to the wrong function.

// Bad:
// The following line returns an error that Parse failed for the input value,
// whereas the failure was that there is no mapping for missingKey.
return Parse(Lookup(missingKey))

Go’s support for multiple return values provides a better solution (see the Effective Go section on multiple returns). Instead of requiring clients to check for an in-band error value, a function should return an additional value to indicate whether its other return values are valid. This return value may be an error or a boolean when no explanation is needed, and should be the final return value.

// Good:
// Lookup returns the value for key or ok=false if there is no mapping for key.
func Lookup(key string) (value string, ok bool)

This API prevents the caller from incorrectly writing Parse(Lookup(key)) which causes a compile-time error, since Lookup(key) has 2 outputs.

Returning errors in this way encourages more robust and explicit error handling:

// Good:
value, ok := Lookup(key)
if !ok {
    return fmt.Errorf("no value for %q", key)
}
return Parse(value)

Some standard library functions, like those in package strings, return in-band error values. This greatly simplifies string-manipulation code at the cost of requiring more diligence from the programmer. In general, Go code in the Google codebase should return additional values for errors.

Indent error flow

Handle errors before proceeding with the rest of your code. This improves the readability of the code by enabling the reader to find the normal path quickly. This same logic applies to any block which tests a condition then ends in a terminal condition (e.g., return, panic, log.Fatal).

Code that runs if the terminal condition is not met should appear after the if block, and should not be indented in an else clause.

// Good:
if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code
// Bad:
if err != nil {
    // error handling
} else {
    // normal code that looks abnormal due to indentation
}

Tip: If you are using a variable for more than a few lines of code, it is generally not worth using the if-with-initializer style. In these cases, it is usually better to move the declaration out and use a standard if statement:

// Good:
x, err := f()
if err != nil {
  // error handling
  return
}
// lots of code that uses x
// across multiple lines
// Bad:
if x, err := f(); err != nil {
  // error handling
  return
} else {
  // lots of code that uses x
  // across multiple lines
}

See Go Tip #1: Line of Sight and TotT: Reduce Code Complexity by Reducing Nesting for more details.