Skip to main content

Style

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 99 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.

Be Consistent

Some of the guidelines outlined in this document can be evaluated objectively; others are situational, contextual, or subjective.

Above all else, be consistent.

Consistent code is easier to maintain, is easier to rationalize, requires less cognitive overhead, and is easier to migrate or update as new conventions emerge or classes of bugs are fixed.

Conversely, having multiple disparate or conflicting styles within a single codebase causes maintenance overhead, uncertainty, and cognitive dissonance, all of which can directly contribute to lower velocity, painful code reviews, and bugs.

When applying these guidelines to a codebase, it is recommended that changes are made at a package (or larger) level: application at a sub-package level violates the above concern by introducing multiple styles into the same code.

Group Similar Declarations

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



var a = 1
var b = 2



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

var (
a = 1
b = 2
)

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 {
var red = color.New(0xff0000)
var green = color.New(0x00ff00)
var blue = color.New(0x0000ff)

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

...
}

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"
)

Package Names

When naming packages, choose a name that is:

  • All lower-case. No capitals or underscores.
  • Does not need to be renamed using named imports at most call sites.
  • 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.

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.

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"
)

Function Grouping and Ordering

  • 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 struct, const, var definitions.

A 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 {...}

Reduce Nesting

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()
}

Unnecessary Else

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
}

Top-level Variable Declarations

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()
// Since F already states that it returns a string, we don't need to specify
// the type again.

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()
// F returns an object of type myError but we want error.

Prefix Unexported Globals with _

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
// foo.go

const (
defaultPort = 8080
defaultUser = "user"
)

// bar.go

func Bar() {
defaultPort := 9090
...
fmt.Println("Default port", defaultPort)

// We will not see a compile error if the first line of
// Bar() is deleted.
}
// foo.go

const (
_defaultPort = 8080
_defaultUser = "user"
)

Exception: Unexported error values may use the prefix err without the underscore. See Error Naming.

Embedding in Structs

Embedded types (such as mutexes) 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).

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 {
// Bad: A.Lock() and A.Unlock() are
// now available, provide no
// functional benefit, and allow
// users to control details about
// the internals of A.
sync.Mutex
}
type countingWriteCloser struct {
// Good: Write() is provided at this
// outer layer for a specific
// purpose, and delegates work
// to the inner type's Write().
io.WriteCloser

count int
}

func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
type Book struct {
// Bad: pointer changes zero value usefulness
io.ReadWriter

// other fields
}

// later

var b Book
b.Read(...) // panic: nil pointer
b.String() // panic: nil pointer
b.Write(...) // panic: nil pointer
type Book struct {
// Good: has useful zero value
bytes.Buffer

// other fields
}

// later

var b Book
b.Read(...) // ok
b.String() // ok
b.Write(...) // ok
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
}

Local Variable Declarations

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)
}
}
}

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.

    BadGood
    if x == "" {
    return []int{}
    }
    if x == "" {
    return nil
    }
  • To check if a slice is empty, always use len(s) == 0. Do not check for nil.

    BadGood
    func isEmpty(s []string) bool {
    return s == nil
    }
    func isEmpty(s []string) bool {
    return len(s) == 0
    }
  • The zero value (a slice declared with var) is usable immediately without make().

    BadGood
    nums := []int{}
    // or, nums := make([]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).

Reduce Scope of Variables

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

Avoid Naked Parameters

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

BadGood
// func printInfo(name string, isLocal, done bool)

printInfo("foo", true, true)
// func printInfo(name string, isLocal, done bool)

printInfo("foo", true /* isLocal */, true /* done */)

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
// Maybe we will have a StatusInProgress in the future.
)

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

Use Raw String Literals to Avoid Escaping

Go supports raw string literals, which can span multiple lines and include quotes. Use these to avoid hand-escaped strings which are much harder to read.

BadGood
wantError := "unknown name:\"test\""
wantError := `unknown error:"test"`

Initializing Structs

Use Field Names to Initialize Structs

You should almost always specify field names when initializing structs. This is now enforced by go vet.

BadGood
k := User{"John", "Doe", true}
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}

Exception: Field names may be omitted in test tables when there are 3 or fewer fields.

tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}

Omit Zero Value Fields in Structs

When initializing structs with field names, omit fields that have zero values unless they provide meaningful context. Otherwise, let Go set these to zero values automatically.

BadGood
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
user := User{
FirstName: "John",
LastName: "Doe",
}

This helps reduce noise for readers by omitting values that are default in that context. Only meaningful values are specified.

Include zero values where field names provide meaningful context. For example, test cases in Test Tables can benefit from names of fields even when they are zero-valued.

tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}

Use var for Zero Value Structs

When all the fields of a struct are omitted in a declaration, use the var form to declare the struct.

BadGood
user := User{}
var user User

This differentiates zero valued structs from those with non-zero fields similar to the distinction created for map initialization, and matches how we prefer to declare empty slices.

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"}

// inconsistent
sptr := new(T)
sptr.Name = "bar"
sval := T{Name: "foo"}

sptr := &T{Name: "bar"}

Initializing Maps

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 is safe to read and write;
// m2 will panic on writes.
m1 = map[T1]T2{}
m2 map[T1]T2
)
var (
// m1 is safe to read and write;
// m2 will panic on writes.
m1 = make(map[T1]T2)
m2 map[T1]T2
)

Declaration and initialization are visually similar.

Declaration and initialization are visually distinct.

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).

Format Strings outside Printf

If you declare format strings for Printf-style functions outside a string literal, make them const values.

This helps go vet perform static analysis of the format string.

BadGood
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

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 Wrap. go 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.