Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Bad

Good

Code Block
languagego
// package foo

func Open() error {
  return errors.New("could not open")
}

// package bar

  if err := foo.Open(); err != nil {
    if err.Error() == "could not open" {
      // handle
    } else {
      panic("unknown error")
    }
  }
Code Block
languagego
// package foo

var ErrCouldNotOpen = errors.New("could not open")

func Open() error {
  return ErrCouldNotOpen
}

// package bar

if err := foo.Open(); err != nil {
  if errors.Is(err, foo.ErrCouldNotOpen) {
    // handle
  } else {
    return errors.Wrap(err, "unknown error")
  }
}

If you have an error that clients may need to detect, and you would like to add more information to it (e.g., it is not a static string), then you should use a custom type.

Bad

Good

Code Block
languagego
func open(file string) error {
  return fmt.Errorf("file %q not found", file)
}

func use() error {
  if err := open(); err != nil {
    if strings.Contains(err.Error(), "not found") {
      // handle
    } else {
      panic("unknown error")
    }
  }
}
Code Block
languagego
type errNotFound struct {
  file string
}

func (e errNotFound) Error() string {
  return fmt.Sprintf("file %q not found", e.file)
}

func open(file string) error {
  return errNotFound{file: file}
}

func use() error {
  if err := open(); err != nil {
    var nfErr errNotFound
    if errors.As(err, &nfErr); ok {
      // handle
    } else {
      return errors.Wrap(err, "opening file")
    }
  }
}

...

  • Return the original error if there is no additional context to add and you want to maintain the original error type.

  • Add context using errors.Wrap so that the error message provides more context and errors.Unwrap can be used to extract the original error. Prefer this to fmt.Errorf("some message %w").

  • If you pass an error through a channel from one goroutine to another, use errors.WithStack on both ends to identify this flow.

  • Use errors.Newf if the callers do not need to detect or handle that specific error case.

  • Use errors.Handle or errors.NewAssertionErrorWithWrappedErrf to hide the original cause.

...

The single return value form of a type assertion will panic on an incorrect type. Therefore, always use the "comma ok" idiom.

Bad

Good

Code Block
languagego
t := i.(string)
Code Block
languagego
t, ok := i.(string)
if !ok {
  // handle the error gracefully
}

...

Code running in production must avoid panics. Panics are a major source of cascading failures. If an error occurs, the function must return an error and allow the caller to decide how to handle it.

Bad

Good

Code Block
languagego
func foo(bar string) {
  if len(bar) == 0 {
    panic("bar must not be empty")
  }
  // ...
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  foo(os.Args[1])
}
Code Block
languagego
func foo(bar string) error {
  if len(bar) == 0
    return errors.New("bar must not be empty")
  }
  // ...
  return nil
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  if err := foo(os.Args[1]); err != nil {
    panic(err)
  }
}

...

Even in tests, prefer t.Fatal or t.FailNow over panics to ensure that the test is marked as failed.

Bad

Good

Code Block
languagego
// func TestFoo(t *testing.T)

f, err := ioutil.TempFile("", "test")
if err != nil {
  panic("failed to set up test")
}
Code Block
languagego
// func TestFoo(t *testing.T)

f, err := ioutil.TempFile("", "test")
if err != nil {
  t.Fatal("failed to set up test")
}

...