美文网首页
Go code standard

Go code standard

作者: 郝炜 | 来源:发表于2022-08-03 17:29 被阅读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:

    • 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, not net/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 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:

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

    Reference

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

    相关文章

      网友评论

          本文标题:Go code standard

          本文链接:https://www.haomeiwen.com/subject/pmcewrtx.html