Go (Golang) coding guidelines
- 1 Introduction
- 2 Guidelines
- 3 Performance
- 4 Style
- 4.1 Code comments
- 4.2 Line Length
- 4.3 Wrapping Function Signatures
- 4.4 Group Similar Declarations
- 4.5 Import Group Ordering
- 4.6 Package Names
- 4.7 Function Names
- 4.8 Import Aliasing
- 4.9 Function Grouping and Ordering
- 4.10 Reduce Nesting
- 4.11 Unnecessary Else
- 4.12 Top-level Variable Declarations
- 4.13 Use Field Names to initialize Structs
- 4.14 Local Variable Declarations
- 4.15 nil is a valid slice
- 4.16 Reduce Scope of Variables
- 4.17 Avoid Naked Parameters
- 4.18 Try to avoid bool parameters
- 4.19 fmt Verbs
- 4.20 Use Raw String Literals to Avoid Escaping
- 4.21 Initializing Struct References
- 4.22 Format Strings outside Printf
- 4.23 Naming Printf-style Functions
- 4.24 Go implementation of SQL
- 5 Patterns
- 5.1 Data-driven tests
- 5.1.1 Scope of Tested Behavior
- 5.2 Test Tables
- 5.3 Functional Options
- 5.1 Data-driven tests
This coding guide is inspired from the Uber Go style guide at https://github.com/uber-go/guide/blob/master/style.md, with modifications specific to Cockroach Labs.
Introduction
These guidelines are the conventions that govern our code. These conventions cover far more than just source file formatting—gofmt handles that for us.
The goal of this guide is to manage this complexity by describing in detail the Dos and Don'ts of writing Go code at Cockroach Labs. These rules exist to keep the code base manageable while still allowing engineers to use Go language features productively.
This documents idiomatic conventions in Go code that we follow at CRL. A lot of these are general guidelines for Go, while others extend upon external resources:
All code should be error-free when run through golint
and go vet
. We recommend setting up your editor to:
Run
crlfmt
on saveRun make lintshort to check for errors
You can find information in editor support for Go tools here: golang/go/wiki/IDEsAndTextEditorPlugins
Guidelines
Pointers to Interfaces
You almost never need a pointer to an interface. You should be passing interfaces as values—the underlying data can still be a pointer.
An interface is two fields:
A pointer to some type-specific information. You can think of this as "type."
Data pointer. If the data stored is a pointer, it’s stored directly. If the data stored is a value, then a pointer to the value is stored.
If you want interface methods to modify the underlying data, you must use a pointer.
Receivers and Interfaces
Methods with value receivers can be called on pointers as well as values.
For example,
type S struct {
data string
}
func (s S) Read() string {
return s.data
}
func (s *S) Write(str string) {
s.data = str
}
sVals := map[int]S{1: {"A"}}
// With a value you can call read, but not Write.
sVals[1].Read()
// This will not compile:
// sVals[0].Write("test")
sPtrs := map[int]*S{1: {"A"}}
// You can call both Read and Write using a pointer
sPtrs[1].Read()
sPtrs[1].Write("test")
Similarly, an interface can be satisfied by a pointer, even if the method has a value receiver.
type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}
var i F
i = s1Val
i = s1Ptr
i = s2Ptr
// The following doesn't compile, since s2Val is a value, and there is no value receiver for f.
// i = s2Val
Effective Go has a good write up on Pointers vs. Values.
Zero-value Mutexes are Valid
The zero-value of sync.Mutex
and sync.RWMutex
is valid, so you almost never need a pointer to a mutex.
Bad | Good |
---|---|
mu := new(sync.Mutex)
mu.Lock() |
If you use a struct by pointer, then the mutex can be a non-pointer field or, preferably, embedded directly into the struct.
Embed for private types or types that need to implement the Mutex interface. | For exported types, use a private lock. |
Copy Slices and Maps when necessary
Slices and maps contain pointers to the underlying data so be conscious of ownership when passing them as parameters or returning them as values.
On the other hand, do not unnecessarily copy slices - heap allocations are expensive and we want to avoid them when possible.
Receiving Slices and Maps
Keep in mind that users can modify a map or slice you received as an argument if you store a reference to it.
If your API captures a slice by reference, document it in the function header.
Bad | Good |
---|---|
Returning Slices and Maps
Similarly, be wary of user modifications to maps or slices exposing internal state.
Your exported API should refrain from returning internal state that otherwise has accessors, e.g. when it is hidden behind a mutex.
For "internal" functions there is more flexibility, although the function name should reflect this (see e.g. log.closeFileLocked
in pkg/util/log
).
Bad | Good |
---|---|
Defer to Clean Up
Use defer to clean up resources such as files and locks.
Bad | Good |
---|---|
Defer has an extremely small overhead and should be avoided only if you can prove that your function execution time is in the order of nanoseconds. The readability win of using defers is worth the minuscule cost of using them. This is especially true for larger methods that have more than simple memory accesses, where the other computations are more significant than the defer
Sometimes, you may wish to Lock a mutex, do some work, and the Unlock it before the end of the function has been reached. If the work being done in this critical section has the potential to panic
, this could end up leaving the mutex locked if the executing code is wrapped in a panic handler, leading to deadlocks.
To protect against this, execute the code in a separate function so that defer
can still be used. This way, even if a panic occurs in the critical section, the mutex will be released.
Bad | Good |
---|---|
Channel Size is One or None
Channels should usually have a size of one or be unbuffered. By default, channels are unbuffered and have a size of zero. Any other size must be subject to a high level of scrutiny. Consider how the size is determined, what prevents the channel from filling up under load and blocking writers, and what happens when this occurs.
Bad | Good |
---|---|
Start Enums at One
The standard way of introducing enumerations in Go is to declare a custom type and a const
group with iota
. Since variables have a 0 default value, you should usually start your enums on a non-zero value.
Bad | Good |
---|---|
There are cases where using the zero value makes sense, for example when the zero value case is the desirable default behavior.
Error handling
The following sub-section are an excerpt from Error concepts and handling in CockroachDB . Read the full page for more background and context.
Performance
Performance-specific guidelines apply only to the hot path.
Be mindful that strconv is faster than fmt
When converting primitives to/from strings, strconv
is faster than fmt
.
Bad | Good |
---|---|
Avoid string-to-byte conversion
Do not create byte slices from a fixed string repeatedly: it causes a heap allocation and a copy. Instead, perform the conversion once and capture the result.
Bad |
---|