Skip to main content

Code Guidelines

General Guidelines

  • Don’t use log.Fatal() or os.Exit() outside of the main. It immediately terminates the program and all defers are ignored and no graceful shutdown is possible. It can lead to inconsistencies. Propagate the error up to the main and let the main function exit instead. Avoid panics as well, almost always use errors. Example.
  • Don’t duplicate code, reuse it. In tests too. Example: duplicate1 and duplicate2
  • Unhandled errors can cause bugs and make it harder to diagnose problems. Try to handle all errors: propagate them to the caller or log them. Even if the function call is used with a defer, and it’s inconvenient to handle the error it returns, still handle it. Wrap the function call in an anonymous function assign error to the upper error like that:
    defer func() {
cerr := f.Close()
if err == nil {
err = errors.Wrap(cerr, "failed to close file")
}
}()
  • Wrap errors with errors.Wrap() when returning them to the caller. It adds the stack trace and a custom block to the error. Without that information investigating an issue is very hard.
  • Use errors.Is() instead of direct errors comparison. This function unwraps errors recursively. Example.
  • Propagate ctx and use APIs that accept ctx, start exposing APIs that accept ctx. Context is a native way for timeouts/cancellation in Go. It allows writing more resilient and fault tolerant code. Example.
  • Don’t shadow builtin functions like copy, len, new etc. Example.
  • Don’t shadow imported packages. Example.
  • Don’t do [:] on a slice. It has no effect. Example.
  • Avoid naked returns if the function isn’t very small. It makes the code more readable.
  • Define explicit constants for strings that are used 3 times or more. It makes the code more maintainable.
  • Define explicit constants for all numbers. It makes the code more readable.
  • Don’t write really long and complex functions. Split them into smaller ones.
  • Treat comments as regular text/documentation. Start with a capital letter, set space after // and end them with a dot. It’s a good habit since Go package docs are generated automatically from the comments and displayed on the godoc site.

Error Handling

We use the new error wrapping API and behavior introduced with Go 1.13 but we use the "github.com/pkg/errors" drop-in replacement which follows the Go 2 design draft and which enables us to have a stack trace for every "wrapping" of the error.

Errors should always be wrapped and annotated with an additional block at each step. The following example shows how errors are wrapped and turned into the corresponding sentinel errors.

package example

import (
"fmt"
"3rdPartyLibrary"

"github.com/pkg/errors"
)

// define error variables to make errors identifiable (sentinel errors)
var ErrSentinel = errors.New("identifiable error")

// turn anonymous 3rd party errors into identifiable ones
func SentinelErrFrom3rdParty() (result interface{}, err error)
if result, err = 3rdPartyLibrary.DoSomething(); err != nil {
err = errors.WithMessagef(ErrSentinel, "failed to do something (%s)", err.Error())
return
}

return
}

// wrap recursive errors at each step
func WrappedErrFromInternalCall() error {
_, err := SentinelErrFrom3rdParty()
return errors.Wrap(err, "wrapped internal error")
}

// create "new" identifiable internal errors that are not originating in 3rd party libs
func ErrFromInternalCall() error {
return errors.WithMessage(ErrSentinel, "internal error")
}

// main function
func main() {
err1 := WrappedErrFromInternalCall()
if errors.Is(err1, ErrSentinel) {
fmt.Printf("%v\n", err1)
}

err2 := ErrFromInternalCall()
if errors.Is(err2 , ErrSentinel) {
fmt.Printf("%v\n", err2 )
}
}