[关闭]
@zhongjianxin 2020-02-11T18:20:27.000000Z 字数 30943 阅读 701

Uber Go Style

AFS-Golang


Go语言入门/开源/标准

Editing this document:

Code Samples:

Use 2 spaces to indent. Horizontal real estate is important in side-by-side
samples.

For side-by-side code samples, use the following snippet.

~~~

BadGood
  1. BAD CODE GOES HERE
  1. GOOD CODE GOES HERE
  1. type S struct {
  2. data string
  3. }
  4. func (s S) Read() string {
  5. return s.data
  6. }
  7. func (s *S) Write(str string) {
  8. s.data = str
  9. }
  10. sVals := map[int]S{1: {"A"}}
  11. // You can only call Read using a value
  12. sVals[1].Read()
  13. // This will not compile:
  14. // sVals[1].Write("test")
  15. sPtrs := map[int]*S{1: {"A"}}
  16. // You can call both Read and Write using a pointer
  17. sPtrs[1].Read()
  18. sPtrs[1].Write("test")
  1. type F interface {
  2. f()
  3. }
  4. type S1 struct{}
  5. func (s S1) f() {}
  6. type S2 struct{}
  7. func (s *S2) f() {}
  8. s1Val := S1{}
  9. s1Ptr := &S1{}
  10. s2Val := S2{}
  11. s2Ptr := &S2{}
  12. var i F
  13. i = s1Val
  14. i = s1Ptr
  15. i = s2Ptr
  16. // The following doesn't compile, since s2Val is a value, and there is no value receiver for f.
  17. // i = s2Val
BadGood
  1. mu := new(sync.Mutex)
  2. mu.Lock()
  1. var mu sync.Mutex
  2. mu.Lock()
  1. type smap struct {
  2. sync.Mutex // only for unexported types
  3. data map[string]string
  4. }
  5. func newSMap() *smap {
  6. return &smap{
  7. data: make(map[string]string),
  8. }
  9. }
  10. func (m *smap) Get(k string) string {
  11. m.Lock()
  12. defer m.Unlock()
  13. return m.data[k]
  14. }
  1. type SMap struct {
  2. mu sync.Mutex
  3. data map[string]string
  4. }
  5. func NewSMap() *SMap {
  6. return &SMap{
  7. data: make(map[string]string),
  8. }
  9. }
  10. func (m *SMap) Get(k string) string {
  11. m.mu.Lock()
  12. defer m.mu.Unlock()
  13. return m.data[k]
  14. }
Embed for private types or types that need to implement the Mutex interface. For exported types, use a private field.
Bad Good
  1. func (d *Driver) SetTrips(trips []Trip) {
  2. d.trips = trips
  3. }
  4. trips := ...
  5. d1.SetTrips(trips)
  6. // Did you mean to modify d1.trips?
  7. trips[0] = ...
  1. func (d *Driver) SetTrips(trips []Trip) {
  2. d.trips = make([]Trip, len(trips))
  3. copy(d.trips, trips)
  4. }
  5. trips := ...
  6. d1.SetTrips(trips)
  7. // We can now modify trips[0] without affecting d1.trips.
  8. trips[0] = ...

Returning Slices and Maps

Similarly, be wary of user modifications to maps or slices exposing internal
state.

BadGood
  1. type Stats struct {
  2. mu sync.Mutex
  3. counters map[string]int
  4. }
  5. // Snapshot returns the current stats.
  6. func (s *Stats) Snapshot() map[string]int {
  7. s.mu.Lock()
  8. defer s.mu.Unlock()
  9. return s.counters
  10. }
  11. // snapshot is no longer protected by the mutex, so any
  12. // access to the snapshot is subject to data races.
  13. snapshot := stats.Snapshot()
  1. type Stats struct {
  2. mu sync.Mutex
  3. counters map[string]int
  4. }
  5. func (s *Stats) Snapshot() map[string]int {
  6. s.mu.Lock()
  7. defer s.mu.Unlock()
  8. result := make(map[string]int, len(s.counters))
  9. for k, v := range s.counters {
  10. result[k] = v
  11. }
  12. return result
  13. }
  14. // Snapshot is now a copy.
  15. snapshot := stats.Snapshot()

Defer to Clean Up

Use defer to clean up resources such as files and locks.

BadGood
  1. p.Lock()
  2. if p.count < 10 {
  3. p.Unlock()
  4. return p.count
  5. }
  6. p.count++
  7. newCount := p.count
  8. p.Unlock()
  9. return newCount
  10. // easy to miss unlocks due to multiple returns
  1. p.Lock()
  2. defer p.Unlock()
  3. if p.count < 10 {
  4. return p.count
  5. }
  6. p.count++
  7. return p.count
  8. // more readable

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

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.

BadGood
  1. // Ought to be enough for anybody!
  2. c := make(chan int, 64)
  1. // Size of one
  2. c := make(chan int, 1) // or
  3. // Unbuffered channel, size of zero
  4. c := make(chan int)

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.

BadGood
  1. type Operation int
  2. const (
  3. Add Operation = iota
  4. Subtract
  5. Multiply
  6. )
  7. // Add=0, Subtract=1, Multiply=2
  1. type Operation int
  2. const (
  3. Add Operation = iota + 1
  4. Subtract
  5. Multiply
  6. )
  7. // Add=1, Subtract=2, Multiply=3

There are cases where using the zero value makes sense, for example when the
zero value case is the desirable default behavior.

  1. type LogOutput int
  2. const (
  3. LogToStdout LogOutput = iota
  4. LogToFile
  5. LogToRemote
  6. )
  7. // LogToStdout=0, LogToFile=1, LogToRemote=2

Use "time" to handle time

Time is complicated. Incorrect assumptions often made about time include the
following.

  1. A day has 24 hours
  2. An hour has 60 minutes
  3. A week has 7 days
  4. A year has 365 days
  5. And a lot more

For example, 1 means that adding 24 hours to a time instant will not always
yield a new calendar day.

Therefore, always use the ["time"] package when dealing with time because it
helps deal with these incorrect assumptions in a safer, more accurate manner.

Use time.Time for instants of time

Use [time.Time] when dealing with instants of time, and the methods on
time.Time when comparing, adding, or subtracting time.

BadGood
  1. func isActive(now, start, stop int) bool {
  2. return start <= now && now < stop
  3. }
  1. func isActive(now, start, stop time.Time) bool {
  2. return (start.Before(now) || start.Equal(now)) && now.Before(stop)
  3. }

Use time.Duration for periods of time

Use [time.Duration] when dealing with periods of time.

BadGood
  1. func poll(delay int) {
  2. for {
  3. // ...
  4. time.Sleep(time.Duration(delay) * time.Millisecond)
  5. }
  6. }
  7. poll(10) // was it seconds or milliseconds?
  1. func poll(delay time.Duration) {
  2. for {
  3. // ...
  4. time.Sleep(delay)
  5. }
  6. }
  7. poll(10*time.Second)

Going back to the example of adding 24 hours to a time instant, the method we
use to add time depends on intent. If we want the same time of the day, but on
the next calendar day, we should use [Time.AddDate]. However, if we want an
instant of time guaranteed to be 24 hours after the previous time, we should
use [Time.Add].

  1. newDay := t.AddDate(0 /* years */, 0, /* months */, 1 /* days */)
  2. maybeNewDay := t.Add(24 * time.Hour)

Use time.Time and time.Duration with external systems

Use time.Duration and time.Time in interactions with external systems when
possible. For example:

When it is not possible to use time.Duration in these interactions, use
int or float64 and include the unit in the name of the field.

For example, since encoding/json does not support time.Duration, the unit
is included in the name of the field.

BadGood
  1. // {"interval": 2}
  2. type Config struct {
  3. Interval int `json:"interval"`
  4. }
  1. // {"intervalMillis": 2000}
  2. type Config struct {
  3. IntervalMillis int `json:"intervalMillis"`
  4. }

When it is not possible to use time.Time in these interactions, unless an
alternative is agreed upon, use string and format timestamps as defined in
RFC 3339. This format is used by default by [Time.UnmarshalText] and is
available for use in Time.Format and time.Parse via [time.RFC3339].

Although this tends to not be a problem in practice, keep in mind that the
"time" package does not support parsing timestamps with leap seconds
(8728), nor does it account for leap seconds in calculations (15190). If
you compare two instants of time, the difference will not include the leap
seconds that may have occurred between those two instants.

Error Types

There are various options for declaring errors:

When returning errors, consider the following to determine the best choice:

If the client needs to detect the error, and you have created a simple error
using [errors.New], use a var for the error.

BadGood
  1. // package foo
  2. func Open() error {
  3. return errors.New("could not open")
  4. }
  5. // package bar
  6. func use() {
  7. if err := foo.Open(); err != nil {
  8. if err.Error() == "could not open" {
  9. // handle
  10. } else {
  11. panic("unknown error")
  12. }
  13. }
  14. }
  1. // package foo
  2. var ErrCouldNotOpen = errors.New("could not open")
  3. func Open() error {
  4. return ErrCouldNotOpen
  5. }
  6. // package bar
  7. if err := foo.Open(); err != nil {
  8. if err == foo.ErrCouldNotOpen {
  9. // handle
  10. } else {
  11. panic("unknown error")
  12. }
  13. }

If you have an error that clients may need to detect, and you would like to add
more information to it (e.g., it is not a static string), then you should use a
custom type.

BadGood
  1. func open(file string) error {
  2. return fmt.Errorf("file %q not found", file)
  3. }
  4. func use() {
  5. if err := open("testfile.txt"); err != nil {
  6. if strings.Contains(err.Error(), "not found") {
  7. // handle
  8. } else {
  9. panic("unknown error")
  10. }
  11. }
  12. }
  1. type errNotFound struct {
  2. file string
  3. }
  4. func (e errNotFound) Error() string {
  5. return fmt.Sprintf("file %q not found", e.file)
  6. }
  7. func open(file string) error {
  8. return errNotFound{file: file}
  9. }
  10. func use() {
  11. if err := open("testfile.txt"); err != nil {
  12. if _, ok := err.(errNotFound); ok {
  13. // handle
  14. } else {
  15. panic("unknown error")
  16. }
  17. }
  18. }

Be careful with exporting custom error types directly since they become part of
the public API of the package. It is preferable to expose matcher functions to
check the error instead.

  1. // package foo
  2. type errNotFound struct {
  3. file string
  4. }
  5. func (e errNotFound) Error() string {
  6. return fmt.Sprintf("file %q not found", e.file)
  7. }
  8. func IsNotFoundError(err error) bool {
  9. _, ok := err.(errNotFound)
  10. return ok
  11. }
  12. func Open(file string) error {
  13. return errNotFound{file: file}
  14. }
  15. // package bar
  16. if err := foo.Open("foo"); err != nil {
  17. if foo.IsNotFoundError(err) {
  18. // handle
  19. } else {
  20. panic("unknown error")
  21. }
  22. }

Error Wrapping

There are three main options for propagating errors if a call fails:

It is recommended to add context where possible so that instead of a vague
error such as "connection refused", you get more useful errors such as
"call service foo: connection refused".

When adding context to returned errors, keep the context succinct by avoiding
phrases like "failed to", which state the obvious and pile up as the error
percolates up through the stack:

BadGood
  1. s, err := store.New()
  2. if err != nil {
  3. return fmt.Errorf(
  4. "failed to create new store: %s", err)
  5. }
  1. s, err := store.New()
  2. if err != nil {
  3. return fmt.Errorf(
  4. "new store: %s", err)
  5. }
  1. failed to x: failed to y: failed to create new store: the error
  1. x: y: new store: the error

However once the error is sent to another system, it should be clear the
message is an error (e.g. an err tag or "Failed" prefix in logs).

See also Don't just check errors, handle them gracefully.

Handle Type Assertion Failures

The single return value form of a type assertion will panic on an incorrect
type. Therefore, always use the "comma ok" idiom.

BadGood
  1. t := i.(string)
  1. t, ok := i.(string)
  2. if !ok {
  3. // handle the error gracefully
  4. }

Don't Panic

Code running in production must avoid panics. Panics are a major source of
cascading failures. If an error occurs, the function must return an error and
allow the caller to decide how to handle it.

BadGood
  1. func foo(bar string) {
  2. if len(bar) == 0 {
  3. panic("bar must not be empty")
  4. }
  5. // ...
  6. }
  7. func main() {
  8. if len(os.Args) != 2 {
  9. fmt.Println("USAGE: foo <bar>")
  10. os.Exit(1)
  11. }
  12. foo(os.Args[1])
  13. }
  1. func foo(bar string) error {
  2. if len(bar) == 0 {
  3. return errors.New("bar must not be empty")
  4. }
  5. // ...
  6. return nil
  7. }
  8. func main() {
  9. if len(os.Args) != 2 {
  10. fmt.Println("USAGE: foo <bar>")
  11. os.Exit(1)
  12. }
  13. if err := foo(os.Args[1]); err != nil {
  14. panic(err)
  15. }
  16. }

Panic/recover is not an error handling strategy. A program must panic only when
something irrecoverable happens such as a nil dereference. An exception to this is
program initialization: bad things at program startup that should abort the
program may cause panic.

  1. var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))

Even in tests, prefer t.Fatal or t.FailNow over panics to ensure that the
test is marked as failed.

BadGood
  1. // func TestFoo(t *testing.T)
  2. f, err := ioutil.TempFile("", "test")
  3. if err != nil {
  4. panic("failed to set up test")
  5. }
  1. // func TestFoo(t *testing.T)
  2. f, err := ioutil.TempFile("", "test")
  3. if err != nil {
  4. t.Fatal("failed to set up test")
  5. }

Use go.uber.org/atomic

Atomic operations with the sync/atomic package operate on the raw types
(int32, int64, etc.) so it is easy to forget to use the atomic operation to
read or modify the variables.

go.uber.org/atomic adds type safety to these operations by hiding the
underlying type. Additionally, it includes a convenient atomic.Bool type.

BadGood
  1. type foo struct {
  2. running int32 // atomic
  3. }
  4. func (f* foo) start() {
  5. if atomic.SwapInt32(&f.running, 1) == 1 {
  6. // already running…
  7. return
  8. }
  9. // start the Foo
  10. }
  11. func (f *foo) isRunning() bool {
  12. return f.running == 1 // race!
  13. }
  1. type foo struct {
  2. running atomic.Bool
  3. }
  4. func (f *foo) start() {
  5. if f.running.Swap(true) {
  6. // already running…
  7. return
  8. }
  9. // start the Foo
  10. }
  11. func (f *foo) isRunning() bool {
  12. return f.running.Load()
  13. }

Avoid Mutable Globals

Avoid mutating global variables, instead opting for dependency injection.
This applies to function pointers as well as other kinds of values.

BadGood
  1. // sign.go
  2. var _timeNow = time.Now
  3. func sign(msg string) string {
  4. now := _timeNow()
  5. return signWithTime(msg, now)
  6. }
  1. // sign.go
  2. type signer struct {
  3. now func() time.Time
  4. }
  5. func newSigner() *signer {
  6. return &signer{
  7. now: time.Now,
  8. }
  9. }
  10. func (s *signer) Sign(msg string) string {
  11. now := s.now()
  12. return signWithTime(msg, now)
  13. }
  1. // sign_test.go
  2. func TestSign(t *testing.T) {
  3. oldTimeNow := _timeNow
  4. _timeNow = func() time.Time {
  5. return someFixedTime
  6. }
  7. defer func() { _timeNow = oldTimeNow }()
  8. assert.Equal(t, want, sign(give))
  9. }
  1. // sign_test.go
  2. func TestSigner(t *testing.T) {
  3. s := newSigner()
  4. s.now = func() time.Time {
  5. return someFixedTime
  6. }
  7. assert.Equal(t, want, s.Sign(give))
  8. }

Avoid Embedding Types in Public Structs

These embedded types leak implementation details, inhibit type evolution, and
obscure documentation.

Assuming you have implemented a variety of list types using a shared
AbstractList, avoid embedding the AbstractList in your concrete list
implementations.
Instead, hand-write only the methods to your concrete list that will delegate
to the abstract list.

  1. type AbstractList struct {}
  2. // Add adds an entity to the list.
  3. func (l *AbstractList) Add(e Entity) {
  4. // ...
  5. }
  6. // Remove removes an entity from the list.
  7. func (l *AbstractList) Remove(e Entity) {
  8. // ...
  9. }
BadGood
  1. // ConcreteList is a list of entities.
  2. type ConcreteList struct {
  3. *AbstractList
  4. }
  1. // ConcreteList is a list of entities.
  2. type ConcreteList struct {
  3. list *AbstractList
  4. }
  5. // Add adds an entity to the list.
  6. func (l *ConcreteList) Add(e Entity) {
  7. return l.list.Add(e)
  8. }
  9. // Remove removes an entity from the list.
  10. func (l *ConcreteList) Remove(e Entity) {
  11. return l.list.Remove(e)
  12. }

Go allows type embedding as a compromise between inheritance and composition.
The outer type gets implicit copies of the embedded type's methods.
These methods, by default, delegate to the same method of the embedded
instance.

The struct also gains a field by the same name as the type.
So, if the embedded type is public, the field is public.
To maintain backward compatibility, every future version of the outer type must
keep the embedded type.

An embedded type is rarely necessary.
It is a convenience that helps you avoid writing tedious delegate methods.

Even embedding a compatible AbstractList interface, instead of the struct,
would offer the developer more flexibility to change in the future, but still
leak the detail that the concrete lists use an abstract implementation.

BadGood
  1. // AbstractList is a generalized implementation
  2. // for various kinds of lists of entities.
  3. type AbstractList interface {
  4. Add(Entity)
  5. Remove(Entity)
  6. }
  7. // ConcreteList is a list of entities.
  8. type ConcreteList struct {
  9. AbstractList
  10. }
  1. // ConcreteList is a list of entities.
  2. type ConcreteList struct {
  3. list *AbstractList
  4. }
  5. // Add adds an entity to the list.
  6. func (l *ConcreteList) Add(e Entity) {
  7. return l.list.Add(e)
  8. }
  9. // Remove removes an entity from the list.
  10. func (l *ConcreteList) Remove(e Entity) {
  11. return l.list.Remove(e)
  12. }

Either with an embedded struct or an embedded interface, the embedded type
places limits on the evolution of the type.

Although writing these delegate methods is tedious, the additional effort hides
an implementation detail, leaves more opportunities for change, and also
eliminates indirection for discovering the full List interface in
documentation.

Performance

Performance-specific guidelines apply only to the hot path.

Prefer strconv over fmt

When converting primitives to/from strings, strconv is faster than
fmt.

BadGood
  1. for i := 0; i < b.N; i++ {
  2. s := fmt.Sprint(rand.Int())
  3. }
  1. for i := 0; i < b.N; i++ {
  2. s := strconv.Itoa(rand.Int())
  3. }
  1. BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
  1. BenchmarkStrconv-4 64.2 ns/op 1 allocs/op

Avoid string-to-byte conversion

Do not create byte slices from a fixed string repeatedly. Instead, perform the
conversion once and capture the result.

BadGood
  1. for i := 0; i < b.N; i++ {
  2. w.Write([]byte("Hello world"))
  3. }
  1. data := []byte("Hello world")
  2. for i := 0; i < b.N; i++ {
  3. w.Write(data)
  4. }
  1. BenchmarkBad-4 50000000 22.2 ns/op
  1. BenchmarkGood-4 500000000 3.25 ns/op

Prefer Specifying Map Capacity Hints

Where possible, provide capacity hints when initializing
maps with make().

  1. make(map[T1]T2, hint)

Providing a capacity hint to make() tries to right-size the
map at initialization time, which reduces the need for growing
the map and allocations as elements are added to the map. Note
that the capacity hint is not guaranteed for maps, so adding
elements may still allocate even if a capacity hint is provided.

BadGood
  1. m := make(map[string]os.FileInfo)
  2. files, _ := ioutil.ReadDir("./files")
  3. for _, f := range files {
  4. m[f.Name()] = f
  5. }
  1. files, _ := ioutil.ReadDir("./files")
  2. m := make(map[string]os.FileInfo, len(files))
  3. for _, f := range files {
  4. m[f.Name()] = f
  5. }
`m` is created without a size hint; there may be more allocations at assignment time. `m` is created with a size hint; there may be fewer allocations at assignment time.

Style

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
  1. import "a"
  2. import "b"
  1. import (
  2. "a"
  3. "b"
  4. )

This also applies to constants, variables, and type declarations.

BadGood
  1. const a = 1
  2. const b = 2
  3. var a = 1
  4. var b = 2
  5. type Area float64
  6. type Volume float64
  1. const (
  2. a = 1
  3. b = 2
  4. )
  5. var (
  6. a = 1
  7. b = 2
  8. )
  9. type (
  10. Area float64
  11. Volume float64
  12. )

Only group related declarations. Do not group declarations that are unrelated.

BadGood
  1. type Operation int
  2. const (
  3. Add Operation = iota + 1
  4. Subtract
  5. Multiply
  6. ENV_VAR = "MY_ENV"
  7. )
  1. type Operation int
  2. const (
  3. Add Operation = iota + 1
  4. Subtract
  5. Multiply
  6. )
  7. const ENV_VAR = "MY_ENV"

Groups are not limited in where they can be used. For example, you can use them
inside of functions.

BadGood
  1. func f() string {
  2. var red = color.New(0xff0000)
  3. var green = color.New(0x00ff00)
  4. var blue = color.New(0x0000ff)
  5. ...
  6. }
  1. func f() string {
  2. var (
  3. red = color.New(0xff0000)
  4. green = color.New(0x00ff00)
  5. blue = color.New(0x0000ff)
  6. )
  7. ...
  8. }

Import Group Ordering

There should be two import groups:

This is the grouping applied by goimports by default.

BadGood
  1. import (
  2. "fmt"
  3. "os"
  4. "go.uber.org/atomic"
  5. "golang.org/x/sync/errgroup"
  6. )
  1. import (
  2. "fmt"
  3. "os"
  4. "go.uber.org/atomic"
  5. "golang.org/x/sync/errgroup"
  6. )

Package Names

When naming packages, choose a name that is:

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.

  1. import (
  2. "net/http"
  3. client "example.com/client-go"
  4. trace "example.com/trace/v2"
  5. )

In all other scenarios, import aliases should be avoided unless there is a
direct conflict between imports.

BadGood
  1. import (
  2. "fmt"
  3. "os"
  4. nettrace "golang.net/x/trace"
  5. )
  1. import (
  2. "fmt"
  3. "os"
  4. "runtime/trace"
  5. nettrace "golang.net/x/trace"
  6. )

Function Grouping and Ordering

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
  1. func (s *something) Cost() {
  2. return calcCost(s.weights)
  3. }
  4. type something struct{ ... }
  5. func calcCost(n []int) int {...}
  6. func (s *something) Stop() {...}
  7. func newSomething() *something {
  8. return &something{}
  9. }
  1. type something struct{ ... }
  2. func newSomething() *something {
  3. return &something{}
  4. }
  5. func (s *something) Cost() {
  6. return calcCost(s.weights)
  7. }
  8. func (s *something) Stop() {...}
  9. 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
  1. for _, v := range data {
  2. if v.F1 == 1 {
  3. v = process(v)
  4. if err := v.Call(); err == nil {
  5. v.Send()
  6. } else {
  7. return err
  8. }
  9. } else {
  10. log.Printf("Invalid v: %v", v)
  11. }
  12. }
  1. for _, v := range data {
  2. if v.F1 != 1 {
  3. log.Printf("Invalid v: %v", v)
  4. continue
  5. }
  6. v = process(v)
  7. if err := v.Call(); err != nil {
  8. return err
  9. }
  10. v.Send()
  11. }

Unnecessary Else

If a variable is set in both branches of an if, it can be replaced with a
single if.

BadGood
  1. var a int
  2. if b {
  3. a = 100
  4. } else {
  5. a = 10
  6. }
  1. a := 10
  2. if b {
  3. a = 100
  4. }

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
  1. var _s string = F()
  2. func F() string { return "A" }
  1. var _s = F()
  2. // Since F already states that it returns a string, we don't need to specify
  3. // the type again.
  4. func F() string { return "A" }

Specify the type if the type of the expression does not match the desired type
exactly.

  1. type myError struct{}
  2. func (myError) Error() string { return "error" }
  3. func F() myError { return myError{} }
  4. var _e error = F()
  5. // 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
  1. // foo.go
  2. const (
  3. defaultPort = 8080
  4. defaultUser = "user"
  5. )
  6. // bar.go
  7. func Bar() {
  8. defaultPort := 9090
  9. ...
  10. fmt.Println("Default port", defaultPort)
  11. // We will not see a compile error if the first line of
  12. // Bar() is deleted.
  13. }
  1. // foo.go
  2. const (
  3. _defaultPort = 8080
  4. _defaultUser = "user"
  5. )

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
  1. type Client struct {
  2. version int
  3. http.Client
  4. }
  1. type Client struct {
  2. http.Client
  3. version int
  4. }

Use Field Names to Initialize Structs

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

BadGood
  1. k := User{"John", "Doe", true}
  1. k := User{
  2. FirstName: "John",
  3. LastName: "Doe",
  4. Admin: true,
  5. }

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

  1. tests := []struct{
  2. op Operation
  3. want string
  4. }{
  5. {Add, "add"},
  6. {Subtract, "subtract"},
  7. }

Local Variable Declarations

Short variable declarations (:=) should be used if a variable is being set to
some value explicitly.

BadGood
  1. var s = "foo"
  1. s := "foo"

However, there are cases where the default value is clearer when the var
keyword is used. Declaring Empty Slices, for example.

BadGood
  1. func f(list []int) {
  2. filtered := []int{}
  3. for _, v := range list {
  4. if v > 10 {
  5. filtered = append(filtered, v)
  6. }
  7. }
  8. }
  1. func f(list []int) {
  2. var filtered []int
  3. for _, v := range list {
  4. if v > 10 {
  5. filtered = append(filtered, v)
  6. }
  7. }
  8. }

nil is a valid slice

nil is a valid slice of length 0. This means that,

Reduce Scope of Variables

Where possible, reduce scope of variables. Do not reduce the scope if it
conflicts with Reduce Nesting.

BadGood
  1. err := ioutil.WriteFile(name, data, 0644)
  2. if err != nil {
  3. return err
  4. }
  1. if err := ioutil.WriteFile(name, data, 0644); err != nil {
  2. return err
  3. }

If you need a result of a function call outside of the if, then you should not
try to reduce the scope.

BadGood
  1. if data, err := ioutil.ReadFile(name); err == nil {
  2. err = cfg.Decode(data)
  3. if err != nil {
  4. return err
  5. }
  6. fmt.Println(cfg)
  7. return nil
  8. } else {
  9. return err
  10. }
  1. data, err := ioutil.ReadFile(name)
  2. if err != nil {
  3. return err
  4. }
  5. if err := cfg.Decode(data); err != nil {
  6. return err
  7. }
  8. fmt.Println(cfg)
  9. 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
  1. // func printInfo(name string, isLocal, done bool)
  2. printInfo("foo", true, true)
  1. // func printInfo(name string, isLocal, done bool)
  2. 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.

  1. type Region int
  2. const (
  3. UnknownRegion Region = iota
  4. Local
  5. )
  6. type Status int
  7. const (
  8. StatusReady = iota + 1
  9. StatusDone
  10. // Maybe we will have a StatusInProgress in the future.
  11. )
  12. 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
  1. wantError := "unknown name:\"test\""
  1. wantError := `unknown error:"test"`

Initializing Struct References

Use &T{} instead of new(T) when initializing struct references so that it
is consistent with the struct initialization.

BadGood
  1. sval := T{Name: "foo"}
  2. // inconsistent
  3. sptr := new(T)
  4. sptr.Name = "bar"
  1. sval := T{Name: "foo"}
  2. 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
  1. var (
  2. // m1 is safe to read and write;
  3. // m2 will panic on writes.
  4. m1 = map[T1]T2{}
  5. m2 map[T1]T2
  6. )
  1. var (
  2. // m1 is safe to read and write;
  3. // m2 will panic on writes.
  4. m1 = make(map[T1]T2)
  5. m2 map[T1]T2
  6. )
Declaration and initialization are visually similar. Declaration and initialization are visually distinct.

Where possible, provide capacity hints when initializing
maps with make(). See
Prefer 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
  1. m := make(map[T1]T2, 3)
  2. m[k1] = v1
  3. m[k2] = v2
  4. m[k3] = v3
  1. m := map[T1]T2{
  2. k1: v1,
  3. k2: v2,
  4. k3: v3,
  5. }

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
  1. msg := "unexpected values %v, %v\n"
  2. fmt.Printf(msg, 1, 2)
  1. const msg = "unexpected values %v, %v\n"
  2. 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.

  1. $ go vet -printfuncs=wrapf,statusf

See also go vet: Printf family check.

Patterns

Test Tables

Use table-driven tests with subtests to avoid duplicating code when the core
test logic is repetitive.

BadGood
  1. // func TestSplitHostPort(t *testing.T)
  2. host, port, err := net.SplitHostPort("192.0.2.0:8000")
  3. require.NoError(t, err)
  4. assert.Equal(t, "192.0.2.0", host)
  5. assert.Equal(t, "8000", port)
  6. host, port, err = net.SplitHostPort("192.0.2.0:http")
  7. require.NoError(t, err)
  8. assert.Equal(t, "192.0.2.0", host)
  9. assert.Equal(t, "http", port)
  10. host, port, err = net.SplitHostPort(":8000")
  11. require.NoError(t, err)
  12. assert.Equal(t, "", host)
  13. assert.Equal(t, "8000", port)
  14. host, port, err = net.SplitHostPort("1:8")
  15. require.NoError(t, err)
  16. assert.Equal(t, "1", host)
  17. assert.Equal(t, "8", port)
  1. // func TestSplitHostPort(t *testing.T)
  2. tests := []struct{
  3. give string
  4. wantHost string
  5. wantPort string
  6. }{
  7. {
  8. give: "192.0.2.0:8000",
  9. wantHost: "192.0.2.0",
  10. wantPort: "8000",
  11. },
  12. {
  13. give: "192.0.2.0:http",
  14. wantHost: "192.0.2.0",
  15. wantPort: "http",
  16. },
  17. {
  18. give: ":8000",
  19. wantHost: "",
  20. wantPort: "8000",
  21. },
  22. {
  23. give: "1:8",
  24. wantHost: "1",
  25. wantPort: "8",
  26. },
  27. }
  28. for _, tt := range tests {
  29. t.Run(tt.give, func(t *testing.T) {
  30. host, port, err := net.SplitHostPort(tt.give)
  31. require.NoError(t, err)
  32. assert.Equal(t, tt.wantHost, host)
  33. assert.Equal(t, tt.wantPort, port)
  34. })
  35. }

Test tables make it easier to add context to error messages, reduce duplicate
logic, and add new test cases.

We follow the convention that the slice of structs is referred to as tests
and each test case tt. Further, we encourage explicating the input and output
values for each test case with give and want prefixes.

  1. tests := []struct{
  2. give string
  3. wantHost string
  4. wantPort string
  5. }{
  6. // ...
  7. }
  8. for _, tt := range tests {
  9. // ...
  10. }

Functional Options

Functional options is a pattern in which you declare an opaque Option type
that records information in some internal struct. You accept a variadic number
of these options and act upon the full information recorded by the options on
the internal struct.

Use this pattern for optional arguments in constructors and other public APIs
that you foresee needing to expand, especially if you already have three or
more arguments on those functions.

  1. // package db
  2. type Option interface {
  3. // ...
  4. }
  5. func WithCache(c bool) Option {
  6. // ...
  7. }
  8. func WithLogger(log *zap.Logger) Option {
  9. // ...
  10. }
  11. // Open creates a connection.
  12. func Open(
  13. addr string,
  14. opts ...Option,
  15. ) (*Connection, error) {
  16. // ...
  17. }
  1. db.Open(addr, db.DefaultCache, zap.NewNop())
  2. db.Open(addr, db.DefaultCache, log)
  3. db.Open(addr, false /* cache */, zap.NewNop())
  4. db.Open(addr, false /* cache */, log)
  1. db.Open(addr)
  2. db.Open(addr, db.WithLogger(log))
  3. db.Open(addr, db.WithCache(false))
  4. db.Open(
  5. addr,
  6. db.WithCache(false),
  7. db.WithLogger(log),
  8. )
BadGood
  1. // package db
  2. func Open(
  3. addr string,
  4. cache bool,
  5. logger *zap.Logger
  6. ) (*Connection, error) {
  7. // ...
  8. }

Our suggested way of implementing this pattern is with an Option interface
that holds an unexported method, recording options on an unexported options
struct.

  1. type options struct {
  2. cache bool
  3. logger *zap.Logger
  4. }
  5. type Option interface {
  6. apply(*options)
  7. }
  8. type cacheOption bool
  9. func (c cacheOption) apply(opts *options) {
  10. opts.cache = bool(c)
  11. }
  12. func WithCache(c bool) Option {
  13. return cacheOption(c)
  14. }
  15. type loggerOption struct {
  16. Log *zap.Logger
  17. }
  18. func (l loggerOption) apply(opts *options) {
  19. opts.logger = l.Log
  20. }
  21. func WithLogger(log *zap.Logger) Option {
  22. return loggerOption{Log: log}
  23. }
  24. // Open creates a connection.
  25. func Open(
  26. addr string,
  27. opts ...Option,
  28. ) (*Connection, error) {
  29. options := options{
  30. cache: defaultCache,
  31. logger: zap.NewNop(),
  32. }
  33. for _, o := range opts {
  34. o.apply(&options)
  35. }
  36. // ...
  37. }

Note that there's a method of implementing this pattern with closures but we
believe that the pattern above provides more flexibility for authors and is
easier to debug and test for users. In particular, it allows options to be
compared against each other in tests and mocks, versus closures where this is
impossible. Further, it lets options implement other interfaces, including
fmt.Stringer which allows for user-readable string representations of the
options.

See also,

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注