Go code standard

2022-08-03  本文已影响0人  郝炜

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:

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:

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

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 vars and consts 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:

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:

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.

Reference

https://github.com/uber-go/guide

上一篇下一篇

猜你喜欢

热点阅读