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.
RECOMMENDED Group Similar Declarations
Go supports grouping similar declarations.
Bad | Good |
---|---|
import "a" . import "b" | import ( . "a" . "b" . ) |
This also applies to constants, variables, and type declarations.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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 . ) . } |
RECOMMENDED Package names
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
, notnet/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.
Bad | Good | |
---|---|---|
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.
Bad | Good |
---|---|
import ( . "fmt" . "os" . nettrace "golang.net/x/trace" . ) | import ( . "fmt" . "os" . "runtime/trace" . nettrace "golang.net/x/trace" . ) |
RECOMMENDED 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.
Bad | Good |
---|---|
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()
RECOMMENDED Prefix Unexported Globals with _
Prefix unexported top-level var
s and const
s 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.
Bad | Good |
---|---|
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".
RECOMMENDED Local Variable Declarations
Short variable declarations (:=
) should be used if a variable is being set to some value explicitly.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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) . } . } . } |
RECOMMENDED Reduce Scope of Variables
Where possible, reduce scope of variables. Do not reduce the scope if it conflicts with Reduce Nesting.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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 fornil
.
Bad | Good |
---|---|
if x == "" { . return []int{} . } | if x == "" { . return nil . } |
- The zero value (a slice declared with
var
) is usable immediately withoutmake()
.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
sval := T{Name: "foo"} . sptr := new(T) . sptr.Name = "bar" | sval := T{Name: "foo"} . sptr := &T{Name: "bar"} |
RECOMMENDED 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.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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).
RECOMMENDED: Embedding in Structs
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.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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
.
RECOMMENDED 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.
Bad | Good |
---|---|
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 { ... } |
RECOMMENDED Avoid Naked Parameters
Naked parameters in function calls can hurt readability. Add C-style comments (/* ... */
) for parameter names when their meaning is not obvious.
Bad | Good |
---|---|
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.
Bad | Good |
---|---|
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() . } |
RECOMMENDED Small Code In A Condition Branch
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.
Bad | Good |
---|---|
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.
RECOMMENDED Unnecessary Else
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.
Bad | Good |
---|---|
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 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.