Avoiding Overly Long Lines
Avoid using code lines that require readers to horizontally scroll or excessively rotate the document.
We recommend limiting line length to 99 characters. Authors should break the line before this limit, but it's not a hard rule. It's permissible for code to exceed this limit.
Consistency
Some of the standards outlined in this document are based on subjective judgments, scenarios, or contexts. However, the most crucial aspect is to maintain consistency.
Consistent code is easier to maintain, more rational, demands less learning cost, and is easier to migrate, update, and fix errors when new conventions emerge or errors occur.
Conversely, including multiple completely different or conflicting code styles in a codebase leads to increased maintenance costs, uncertainty, and cognitive biases. All these directly result in slower speed, painful code reviews, and an increased number of bugs.
When applying these standards to a codebase, it's recommended to make changes at the package (or larger) level. Applying multiple styles at the subpackage level violates the above concerns.
Group Similar Declarations
Go language supports grouping similar declarations.
Not Recommended:
import "a"
import "b"
Recommended:
import (
"a"
"b"
)
This also applies to constant, variable, and type declarations:
Not Recommended:
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
Recommended:
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
Only group related declarations together and avoid grouping unrelated declarations.
Not Recommended:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
Recommended:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
There are no restrictions on where to use grouping. For example, you can use them within a function:
Not Recommended:
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
Recommended:
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
Exception: If variable declarations are adjacent to other variables, especially within function-local declarations, they should be grouped together. Perform this even for unrelated variables declared together.
Not Recommended:
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
Recommended:
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
Import Grouping
Imports should be grouped into two categories:
- Standard library
- Other libraries
By default, this is the grouping applied by goimports. Not Recommended:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Recommended:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Package Name
When naming a package, please follow these rules:
- All lowercase, no capital letters or underscores.
- In most cases, no need for renaming when importing.
- Short and concise. Remember that the name is fully qualified everywhere it's used.
- Avoid plurals. For example, use
net/url
instead ofnet/urls
. - Avoid using "common," "util," "shared," or "lib." These are not informative enough.
Function Naming
We adhere to the Go community convention of using MixedCaps for function names. An exception is made for grouping related test cases, where the function name may contain underscores, such as: TestMyFunction_WhatIsBeingTested
.
Import Aliases
If the package name does not match the last element of the import path, an import alias must be used.
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
In all other cases, import aliases should be avoided unless there is a direct conflict between imports. Not Recommended:
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
Recommended:
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
Function Grouping and Order
- Functions should be sorted roughly in the order they are called.
- Functions within the same file should be grouped by receiver.
Therefore, exported functions should appear first in the file, placed after the struct
, const
, and var
definitions.
A newXYZ()
/NewXYZ()
may appear after type definitions but before the remaining methods of the receiver.
As functions are grouped by receiver, general utility functions should appear at the end of the file. Not Recommended:
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{}
}
Recommended:
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 by handling error/special cases as early as possible and either returning or continuing the loop. Reducing nesting reduces the amount of code at multiple levels.
Not Recommended:
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)
}
}
Recommended:
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 being set in both branches of an if, it can be replaced with a single if statement.
Not Recommended:
var a int
if b {
a = 100
} else {
a = 10
}
Recommended:
a := 10
if b {
a = 100
}
Top-level Variable Declaration
At the top level, use the standard var
keyword. Do not specify the type unless it differs from the type of the expression.
Not Recommended:
var _s string = F()
func F() string { return "A" }
Recommended:
var _s = F()
// Since F explicitly returns a string type, we do not need to explicitly specify the type for _s
func F() string { return "A" }
Specify the type if it does not exactly match the type needed for the expression.
type myError struct{}
func (myError) Error() string { return "error" }
func F() myError { return myError{} }
var _e error = F()
// F returns an instance of type myError, but we need error type
Use '_' as a Prefix for Unexported Top-level Constants and Variables
For unexported top-level vars
and consts
, prefix them with an underscore _
to explicitly indicate their global nature when used.
Basic rationale: Top-level variables and constants have package-level scope. Using generic names can easily lead to accidentally using the wrong value in other files.
Not Recommended:
// 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.
}
Recommended:
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
Exception: Unexported error values can use the err
prefix without an underscore. See error naming.
Embedding in Structs
Embedded types (such as mutex) should be placed at the top of the field list within the struct and must have an empty line separating the embedded fields from the regular fields.
Not recommended:
type Client struct {
version int
http.Client
}
Recommended:
type Client struct {
http.Client
version int
}
Embedding should provide tangible benefits, such as adding or enhancing functionality in a semantically appropriate way. It should be used without any adverse impact on the user. (Also see: Avoid embedding types in public structs)
Exceptions: Even in unexported types, Mutex should not be used as an embedded field. Also see: Zero value Mutex is valid.
Embedding should not:
- Solely exist for aesthetics or convenience.
- Make it more difficult to construct or use the outer type.
- Affect the zero value of the outer type. If the outer type has a useful zero value, there should still be a useful zero value after embedding the inner type.
- Have the side effect of exposing unrelated functions or fields from the embedded inner type.
- Expose unexported types.
- Affect the cloning form of the outer type.
- Change the API or type semantics of the outer type.
- Embed the inner type in a non-standard form.
- Expose implementation details of the outer type.
- Allow users to observe or control the internal type.
- Change the general behavior of internal functions in a way that may surprise users.
In short, embed consciously and purposefully. A good litmus test is, "Will all these exported methods/fields from the inner type be directly added to the outer type?" If the answer is some
or no
, do not embed the inner type - use fields instead.
Not recommended:
type A struct {
// Bad: A.Lock() and A.Unlock() are now available
// Provides no functional benefit and allows the user to control internal details of A.
sync.Mutex
}
Recommended:
type countingWriteCloser struct {
// Good: Write() is provided at the outer level for a specific purpose,
// and delegates the work to the Write() of the inner type.
io.WriteCloser
count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
Local Variable Declarations
If a variable is explicitly set to a value, the short variable declaration form (:=
) should be used.
Not recommended:
var s = "foo"
Recommended:
s := "foo"
However, in some cases, using the var
keyword for default values can be clearer.
Not recommended:
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
Recommended:
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 with a length of 0, which means:
- You should not explicitly return a slice with a length of zero. Instead, return
nil
.
Not recommended:
if x == "" {
return []int{}
}
Recommended:
if x == "" {
return nil
}
- To check if a slice is empty, always use
len(s) == 0
instead ofnil
.
Not recommended:
func isEmpty(s []string) bool {
return s == nil
}
Recommended:
func isEmpty(s []string) bool {
return len(s) == 0
}
- Zero value slices (slices declared with
var
) can be used immediately without callingmake()
.
Not recommended:
nums := []int{}
// or, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Recommended:
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Remember, although a nil slice is a valid slice, it is not equal to a slice with a length of 0 (one is nil and the other is not), and they may be treated differently in different situations (e.g., serialization).
Narrow Variable Scope
If possible, try to narrow the scope of variables, unless it conflicts with the rule of reducing nesting.
Not recommended:
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
Recommended:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
If the result of a function call outside the if statement needs to be used, do not attempt to narrow the scope.
Not recommended:
if data, err := os.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
Recommended:
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
Avoid Naked Parameters
Unclear parameters in function calls may harm readability. When the meaning of parameter names is not obvious, add C-style comments (/* ... */
) to the parameters.
Not recommended:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
Recommended:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
For the example above, a better approach could be to replace the bool
types with custom types. This way, the parameter can potentially support more than just two states (true/false) 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 the use of raw string literals, which is indicated by " ` " to represent raw strings. In scenarios where escaping is required, we should use this approach to replace the more difficult-to-read manually escaped strings.
It can span multiple lines and include quotes. Using these strings can avoid more difficult-to-read manually escaped strings.
Not recommended:
wantError := "unknown name:\"test\""
Recommended:
wantError := `unknown error:"test"`
Initialize structs
Initialize structures using field names
When initializing a structure, field names should almost always be specified. This is currently enforced by go vet
.
Not recommended:
k := User{"John", "Doe", true}
Recommended:
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
Exception: When there are 3 or fewer fields, field names in test tables may be omitted.
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
Omit zero-value fields in structures
When initializing a structure with named fields, unless meaningful context is provided, ignore fields with a zero value. That is, let us automatically set these to zero values.
Not recommended:
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
Recommended:
user := User{
FirstName: "John",
LastName: "Doe",
}
This helps reduce reading barriers by omitting default values in the context. Only specify meaningful values.
Include zero value where field names provide meaningful context. For example, test cases in a table-driven test can benefit from naming the fields, even if they are zero values.
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}
Use var
for zero-value structs
If all fields of a struct are omitted in the declaration, use var
to declare the struct.
Not recommended:
user := User{}
Recommended:
var user User
This distinguishes zero-value structs from those with non-zero value fields, similar to what we prefer when declaring an empty slice.
Initialize struct references
When initializing structure references, use &T{}
instead of new(T)
to make it consistent with struct initialization.
Not recommended:
sval := T{Name: "foo"}
// inconsistent
sptr := new(T)
sptr.Name = "bar"
Recommended:
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
Initialize Maps
For an empty map, use make(..)
to initialize it, and the map is filled programmatically. This makes the map initialization different from declaration in appearance, and it also conveniently allows for adding size hints after make.
Not Recommended:
var (
// m1 is read-write safe;
// m2 panics when writing
m1 = map[T1]T2{}
m2 map[T1]T2
)
Recommended:
var (
// m1 is read-write safe;
// m2 panics when writing
m1 = make(map[T1]T2)
m2 map[T1]T2
)
| Declaration and initialization look very similar. | Declaration and initialization look very different. |
Where possible, provide the map capacity size during initialization, see Specifying Map Capacity for details.
Additionally, if the map contains a fixed list of elements, use map literals to initialize the map.
Not Recommended:
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
Recommended:
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}
The basic guideline is to use map literals for adding a fixed set of elements during initialization. Otherwise, use make
(and if possible, specify the map capacity).
String Format for Printf-style Functions
If you declare a Printf
-style function's format string outside a function, set it as a const
constant.
This helps go vet
to perform static analysis on the format string.
Not Recommended:
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
Recommended:
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
Naming Printf-style Functions
When declaring Printf
-style functions, ensure that go vet
can detect and check the format string.
This means you should use predefined Printf
-style function names as much as possible. go vet
will check these by default. For more information, see Printf Family.
If predefined names cannot be used, end the selected name with f
: Wrapf
instead of Wrap
. go vet
can request specific Printf-style names to be checked, but the name must end with f
.
go vet -printfuncs=wrapf,statusf