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.
Bad
import "a"
import "b"
Good
import (
"a"
"b"
)
</td></tr>
</tbody></table>
This also applies to constants, variables, and type declarations.
Bad
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
Good
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.
Bad
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
Good
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
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
// ...
}
Good
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
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
Good
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
Import Group Ordering
There should be two import groups:
- Standard library
- Everything else
This is the grouping applied by goimports by default.
Bad
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Good
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
, notnet/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
.
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
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{}
}
Good
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 {...}
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.
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
// 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.
}
Good
// 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 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
type Client struct {
version int
http.Client
}
Good
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
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
}
Good
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)
}
Bad
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
Good
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
Bad
type Client struct {
sync.Mutex
sync.WaitGroup
bytes.Buffer
url.URL
}
Good
type Client struct {
mtx sync.Mutex
wg sync.WaitGroup
buf bytes.Buffer
url url.URL
}
Reduce Scope of Variables
Where possible, reduce scope of variables. Do not reduce the scope if it
conflicts with Reduce Nesting.
Bad
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
Good
if err := os.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
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
}
Good
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
Naked parameters in function calls can hurt readability. Add C-style comments
(/* ... */
) for parameter names when their meaning is not obvious.
Bad
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
Good
// 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)
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.
Linting
More importantly than any "blessed" set of linters, lint consistently across a
codebase.
We recommend using the following linters at a minimum, because we feel that they
help to catch the most common issues and also establish a high bar for code
quality without being unnecessarily prescriptive:
-
errcheck to ensure that errors are handled
-
goimports to format code and manage imports
-
golint to point out common style mistakes
-
govet to analyze code for common mistakes
-
staticcheck to do various static analysis checks
Lint Runners
We recommend golangci-lint as the go-to lint runner for Go code, largely due
to its performance in larger codebases and ability to configure and use many
canonical linters at once. This repo has an example .golangci.yml config file
with recommended linters and settings.
golangci-lint has various linters available for use. The above linters are
recommended as a base set, and we encourage teams to add any additional linters
that make sense for their projects.
网友评论