Skip to main content

How To Code Go: Style & Format

Why writing with format is important?

By adhering to Go's best practice formatting, we will benefit from improved code readability, maintainability, and collaboration, ultimately contributing to higher quality software and a more efficient development process. It's a small investment that pays off significantly in the long run.

MUST use goimports

Use goimports before committing. goimports is a tool that automatically formats Go source code using Gofmt, in addition to formatting import lines, adding missing ones and removing unreferenced ones.

Most editors/IDEs allow you to run commands before/after saving a file, you can set it up to run goimports so that it’s applied to every file when saving.

For Goland , you can add the tools by Preferences → Tools → File Watchers , add the symbol(+) to add goimports .

MUST Avoid overly long lines

Avoid lines of code that require readers to scroll horizontally or turn their heads too much.

We recommend a soft line length limit of 120 characters. Authors should aim to wrap lines before hitting this limit, but it is not a hard limit. Code is allowed to exceed this limit.

Go supports grouping similar declarations.

BadGood
import "a"
.import "b"
import (
.  "a"
.  "b"
.)

This also applies to constants, variables, and type declarations.

BadGood
const a = 1
.const b = 2
const (
.a = 1
.b = 2
.)
var a = 1
.var b = 2
var (
.a = 1
.b = 2
.)
type Area float64
.type Volume float64
type (
.Area float64
.Volume float64
.)

Only group related declarations. Do not group declarations that are unrelated.

BadGood
type Operation int
.const (
.Add Operation = iota + 1
.Subtract
.Multiply
.EnvVar = "MY_ENV"
.)
type Operation int
.const (
.Add Operation = iota + 1
.Subtract
.Multiply
.)
.const EnvVar = "MY_ENV"

Groups are not limited in where they can be used. For example, you can use them inside of functions.

BadGood
func f() string {
. red := color.New(0xff0000)
. green := color.New(0x00ff00)
. blue := color.New(0x0000ff)
.}
func f() string {
.var (
. red = color.New(0xff0000)
. green = color.New(0x00ff00)
. blue = color.New(0x0000ff)
.)
.}

Exception: Variable declarations, particularly inside functions, should be grouped together if declared adjacent to other variables. Do this for variables declared together even if they are unrelated.

BadGood
func (c *client) request() {
.caller := c.name
.format := "json"
.timeout := 5 * time.Second
.var err error
.}
func (c *client) request() {
.var (
. caller = c.name
. format = "json"
. timeout = 5 * time.Second
. err error
.)
.}

When naming packages, choose a name that is:

  • All lower-case. No capitals or underscores.

  • Choose a name that does not need to be renamed using named imports at most call sites (unambiguous in the most frequent use contexts).

    • As discussed above, we still prefer package names to be unique across the entire project.
    • A good trick to make this work is to take the name of a parent package and add a suffix or prefix.
      For example, server/serverpb, kv/kvserver, util/contextutil, util/testutil, etc.
  • Short and succinct. Remember that the name is identified in full at every call site.

  • Not plural. For example, net/url, not net/urls.

  • Not "common", "util", "shared", or "lib". These are bad, uninformative names.

See also Package Names and Style guideline for Go packages.

MUST Import Group Ordering

There should be two import groups:

  • Standard library
  • Everything else

This is the grouping applied by goimports by default.

BadGood
import (
."fmt"
."os"
."go.uber.org/atomic"
."golang.org/x/sync/errgroup"
.)
import (
."fmt"
."os"
."go.uber.org/atomic"
."golang.org/x/sync/errgroup"
.)

MUST Import Aliasing

Import aliasing must be used if the package name does not match the last element of the import path.

import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)

In all other scenarios, import aliases should be avoided unless there is a direct conflict between imports.

BadGood
import (
."fmt"
."os"
.nettrace "golang.net/x/trace"
.)
import (
."fmt"
."os"
."runtime/trace"
.nettrace "golang.net/x/trace"
.)

At the top level, use the standard var keyword. Do not specify the type, unless it is not the same type as the expression.

BadGood
var _s string = F()
.func F() string {
. return "A"
.}
var _s = F()
.func F() string {
. return "A"
.}

Specify the type if the type of the expression does not match the desired type exactly.

type myError struct{}

func (myError) Error() string {
return "error"
}

func F() myError {
return myError{}
}

var _e error = F()

Prefix unexported top-level vars and consts with _ to make it clear when they are used that they are global symbols.

Exception: Unexported error values, which should be prefixed with err.

Rationale: Top-level variables and constants have a package scope. Using a generic name makes it easy to accidentally use the wrong value in a different file.

BadGood
const (
.defaultPort = 8080
.defaultUser = "user"
.)
.func Bar() {
.defaultPort := 9090
....
.fmt.Println("Default port", defaultPort)
.}
const (
._defaultPort = 8080
._defaultUser = "user"
.)

Exception: Unexported error values may use the prefix err without the underscore. See section "Error Names".

Short variable declarations (:=) should be used if a variable is being set to some value explicitly.

BadGood
var s = "foo"s := "foo"

However, there are cases where the default value is clearer when the var keyword is used. Declaring Empty Slices, for example.

BadGood
func f(list []int) {
. filtered := []int{}
.for _, v := range list {
. if v > 10 {
. filtered = append(filtered, v)
.}
.}
.}
func f(list []int) {
. var filtered []int
.for _, v := range list {
. if v > 10 {
. filtered = append(filtered, v)
.}
.}
.}

Where possible, reduce scope of variables. Do not reduce the scope if it conflicts with Reduce Nesting.

BadGood
err := ioutil.WriteFile(name, data, 0644)
.if err != nil {
. return err
.}
if err := ioutil.WriteFile(name, data, 0644); err != nil {
. return err
.}

If you need a result of a function call outside of the if, then you should not try to reduce the scope.

BadGood
if data, err := ioutil.ReadFile(name); err == nil {
. err = cfg.Decode(data)
. if err != nil {
. return err
. }
. fmt.Println(cfg)
. return nil
. } else {
. return err
. }
data, err := ioutil.ReadFile(name)
. if err != nil {
. return err
. }
. if err := cfg.Decode(data); err != nil {
. return err
. }
. fmt.Println(cfg)
. return nil

MUST nil is a valid slice

nil is a valid slice of length 0. This means that,

  • You should not return a slice of length zero explicitly. Return nil instead.
  • To check if a slice is empty, always use len(s) == 0. Do not check for nil.
BadGood
if x == "" {
. return []int{}
. }
if x == "" {
. return nil
. }
  • The zero value (a slice declared with var) is usable immediately without make().
BadGood
nums := []int{}
.if add1 {
. nums = append(nums, 1)
. }
.if add2 {
. nums = append(nums, 2)
. }
var nums []int
.if add1 {
. nums = append(nums, 1)
. }
.if add2 {
. nums = append(nums, 2)
. }

Remember that, while it is a valid slice, a nil slice is not equivalent to an allocated slice of length 0 - one is nil and the other is not - and the two may be treated differently in different situations (such as serialization).

MUST Initializing Struct References

Use &T{} instead of new(T) when initializing struct references so that it is consistent with the struct initialization.

BadGood
sval := T{Name: "foo"}
.sptr := new(T)
.sptr.Name = "bar"
sval := T{Name: "foo"}
.sptr := &T{Name: "bar"}

Prefer make(..) for empty maps, and maps populated programmatically. This makes map initialization visually distinct from declaration, and it makes it easy to add size hints later if available.

BadGood
var (
. m1 = map[T1]T2{}
. m2 map[T1]T2
.)
var (
. m1 = make(map[T1]T2)
. m2 map[T1]T2
.)

Declaration and initialization are visually similar.

Where possible, provide capacity hints when initializing maps with make(). See Specifying Map Capacity Hints for more information.

On the other hand, if the map holds a fixed list of elements, use map literals to initialize the map.

BadGood
m := make(map[T1]T2, 3)
.m[k1] = v1
.m[k2] = v2
.m[k3] = v3
m := map[T1]T2{
. k1: v1,
. k2: v2,
. k3: v3,
.}

The basic rule of thumb is to use map literals when adding a fixed set of elements at initialization time, otherwise use make (and specify a size hint if available).

Embedded types should be at the top of the field list of a struct, and there must be an empty line separating embedded fields from regular fields.

BadGood
type Client struct {
. version int
. http.Client
.}
type Client struct {
. http.Client
. version int
.}

Embedding should provide tangible benefit, like adding or augmenting functionality in a semantically-appropriate way. It should do this with zero adverse user-facing effects (see also: Avoid Embedding Types in Public Structs).

Exception: Mutexes should not be embedded, even on unexported types. See also: Zero-value Mutexes are Valid.

Embedding should not:

  • Be purely cosmetic or convenience-oriented.
  • Make outer types more difficult to construct or use.
  • Affect outer types' zero values. If the outer type has a useful zero value, it should still have a useful zero value after embedding the inner type.
  • Expose unrelated functions or fields from the outer type as a side-effect of embedding the inner type.
  • Expose unexported types.
  • Affect outer types' copy semantics.
  • Change the outer type's API or type semantics.
  • Embed a non-canonical form of the inner type.
  • Expose implementation details of the outer type.
  • Allow users to observe or control type internals.
  • Change the general behavior of inner functions through wrapping in a way that would reasonably surprise users.

Simply put, embed consciously and intentionally. A good litmus test is, "would all of these exported inner methods/fields be added directly to the outer type"; if the answer is "some" or "no", don't embed the inner type - use a field instead.

BadGood
type A struct {
. sync.Mutex
. }
type countingWriteCloser struct {
. io.WriteCloser
. count int
. }
.
.func (w *countingWriteCloser) Write(bs []byte) (int, error) {
. w.count += len(bs)
. return w.WriteCloser.Write(bs)
. }
type Book struct {
. io.ReadWriter
. }
.var b Book
.b.Read(...)
.b.String()
.b.Write(...)
type Book struct {
. bytes.Buffer
. }
.var b Book
.b.Read(...)
.b.String()
.b.Write(...)
type Client struct {
. sync.Mutex
. sync.WaitGroup
. bytes.Buffer
. url.URL
. }
type Client struct {
. mtx sync.Mutex
. wg sync.WaitGroup
. buf bytes.Buffer
. url url.URL
. }

MUST Error Names

For error values stored as global variables, use the prefix Err or err depending on whether they're exported.

var (
ErrBrokenLink = errors.New("link is broken")
ErrCouldNotOpen = errors.New("could not open")
errNotFound = errors.New("not found")
)

For custom error types, use the suffix Error instead.

type NotFoundError struct {
File string
}

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

type resolveError struct {
Path string
}

func (e *resolveError) Error() string {
return fmt.Sprintf("resolve %q", e.Path)
}

MUST Function Names

We follow the Go community's convention of using MixedCaps for function names. An exception is made for test functions, which may contain underscores for the purpose of grouping related test cases, e.g., TestMyFunction_WhatIsBeingTested.

  • Functions should be sorted in rough call order.
  • Functions in a file should be grouped by receiver.

Therefore, exported functions should appear first in a file, after structconstvar definitions.

newXYZ()/NewXYZ() may appear after the type is defined, but before the rest of the methods on the receiver.

Since functions are grouped by receiver, plain utility functions should appear towards the end of the file.

BadGood
func (s *something) Cost() {
. return calcCost(s.weights)
. }
.type something struct{ ... }
. func calcCost(n []int) int { ... }
.func (s *something) Stop() {...}
.func newSomething() *something {
. return &something{}
. }
type something struct{ ... }
.func newSomething() *something {
. return &something{}
. }
.func (s *something) Cost() {
. return calcCost(s.weights)
. }
.func (s *something) Stop() {...}
. func calcCost(n []int) int { ... }

Naked parameters in function calls can hurt readability. Add C-style comments (/* ... */) for parameter names when their meaning is not obvious.

BadGood
printInfo("foo", true, true)printInfo("foo", true, true)

Better yet, replace naked bool types with custom types for more readable and type-safe code. This allows more than just two states (true/false) for that parameter in the future.

type Region int

const (
UnknownRegion Region = iota
Local
)

type Status int

const (
StatusReady Status = iota + 1
StatusDone
)

func printInfo(name string, region Region, status Status)

MUST Avoid Long Function

If you function is long than 100 lines, you maybe need review it and split it in multi-functions by their responsibility.

MUST Nesting Limitation

The number of nesting levels should less(contain) than 2, and cannot be more and equal than 4.

Code should reduce nesting where possible by handling error cases/special conditions first and returning early or continuing the loop. Reduce the amount of code that is nested multiple levels.

BadGood
for _, v := range data {
. if v.F1 == 1 {
. v = process(v)
. if err := v.Call(); err == nil {
. v.Send()
. } else {
. return err
. }
. } else {
. log.Printf("Invalid v: %v", v)
. }
for _, v := range data {
. if v.F1 != 1 {
. log.Printf("Invalid v: %v", v)
. continue
. }
. v = process(v)
. if err := v.Call(); err != nil {
. return err
. }
. v.Send()
. }

Code in condition branch is always the exception logic code, it will interrupt the reviewer normal thinking process and make it logic unclearly. When the code is complex you should think about to extract it into a function.

BadGood
data, err := TryDoJobA()
.if err != nil {
. ...
. }
data, err := TryDoJobA()
.if err != nil {
. HandlerError(err)
. }
. ...

As a suggestion, the line num of condition branch codes should be less than 10.

Code in else condition branch will bring more thinking and will interrupt the normal code process for reviewer, we should avoid unnecessary else condition branch as possible.

If a variable is set in both branches of an if, it can be replaced with a single if.

BadGood
var a int
.if b {
. a = 100
. } else {
. a = 10
. }
a := 10
.if b {
. a = 100
. }

MUST Naming Printf-style Functions

When you declare a Printf-style function, make sure that go vet can detect it and check the format string.

This means that you should use predefined Printf-style function names if possible. go vet will check these by default. See Printf family for more information.

If using the predefined names is not an option, end the name you choose with f: Wrapf, not Wrapgo vet can be asked to check specific Printf-style names but they must end with f.

$ go vet -printfuncs=wrapf,statusf

See also go vet: Printf family check.