Effective Go

Code specification

  • Code is consistently formatted with gofmt so that the style is consistent
  • Annotation documentation
    • Every package should have documentation, and simple package documentation can be as simple as one or two lines
    • The first line outlines what the package does
    • If it’s a bunch of constant definitions (e.g. Error), the comments can be skipped (liqiang note: this is a bit formal?)
  • package
    • package names should be short, just one lowercase word, no underscores (_) and mixedCaps format
    • Naming need not be too long: Long names don’t automatically make things more readable. A helpful doc comment can often be more valuable than an extra long name.
      • eg: once.Do(setup) better than once.DoOrWaitUntilDone(setup)

Language features

  • Conditional statements
    • If the conditional flow of if ends with return, then the else keyword should not appear, it’s redundant, just put it directly after the if paragraph
    • := If the variables on the left have already been defined before, it depends on the situation:
      • If all the variables on the left have been defined before, then an error will be reported
      • If the variables on the left are partially defined and partially undefined, then it is legal, and the defined variables are not redefined, but are reassigned to the previous variables
    • for
      • If you only need the subscript of slice or the key of map, then you only need to do: for key := range var
      • If you only need the value, then you need to ignore the previous part: for _, v := range var
      • It is legal to delete keys while traversing the map’s key
      • The unit for for traversing string is rune, so Chinese can be handled correctly
    • switch
      • Go’s switch can be used with or without expressions
      • Without expression, it iterate through case from top to bottom, and if case is true, execute the corresponding statement.
      • case supports multiple comma-separated matches
  • if defer is calling a function/method, the value is computed when defer is called, not when the function content is run
  • new just creates a blank structure and does not initialize the memory. For some structures, blank is meaningful, e.g. Mutex, blank means unlocked, so it can be used directly.
  • make can only be used for Array/Slice/Map, it is initialized memory, for example, the initialized array length is 100
  • Arrays
    • Go’s arrays are passed, assigning/referencing an Array is not a pointer, but a copy of the array’s value
    • The size of an array is also part of the type, [10]int and [20]int are two different types and cannot be assigned directly
    • One advantage of arrays over slice is that their memory is fixed, so in the case of multiple arrays, you can just use array[1][2]
  • Both map and slice pass pointers and can modify their contents
  • If map takes a value and the key does not exist, then the value is returned as zero
  • delete Deleting a non-existent map key is legal
  • Constants can only be numbers, multiple characters (runes), strings and bool types
  • Constants are initialized at compile time, including those defined in functions, so they cannot contain runtime information
  • Variables are initialized at runtime
  • interface
    • If the type assertion of interface{} fails, a runtime error is generated
    • If a type exists only to implement an interface and will never have exported methods beyond that interface, the constructor should return an interface value rather than the implementing type.
    • Make sure that a structure already implements an interface by writing: var _ json.Marshaler = (*RawMessage)(nil)
  • Nested Structs
    • When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.
    • Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type.
    • If the same name appears at the same nesting level, it is usually an error;
    • However, if the duplicate name is never mentioned in the program outside the type definition, it is OK.

Concurrency

  • Do not communicate by sharing memory; instead, share memory by communicating.

Thanos Coding Style Guide

Development/Review

  • Reliability

    • CloseWithErrCapture

      1. [root@liqiang.io]# cat runutil.go
      2. ...
      3. // CloseWithErrCapture runs function and on error return error by argument including the given error (usually
      4. // from caller function).
      5. func CloseWithErrCapture(err *error, closer io.Closer, format string, a . . interface{}) {
      6. merr := errutil.MultiError{}
      7. merr.Add(*err)
      8. merr.Add(errors.Wrapf(closer.Close(), format, a ...))
      9. *err = merr.Err()
      10. }
    • don’t use panic, and if a dependency is used, don’t forget to use recover to handle it, and drop the dependency if you can.
    • Don’t use the same variable names in different block scopes (inside and outside of if/for)
  • Performance

    • Pre-allocate the size of slice and map
    • Reuse the underlying array of slice to avoid reallocation
      • messages = messages[:0] can reset slice
  • Readability

    • interface should be as small as possible (1-3 methods)
    • Avoid shallow functions (reduce unnecessary abstraction)
    • If it is clear that the error returned by a function is harmless, it can be ignored with _ =
    • If a variable is only used once, then there is no need to assign the variable, just initialize it directly where it is used
    • function/method parameters, either all in one line, or one line per parameter
    • Structured logs should be more structured, not all information stuffed in one log text

Testing

  • Use table-driven tests, it’s easier to read
  • Don’t rely on real-time time for comparison, use relative time
  • Please use linters
    • go vet
    • golangci-lint
      • deadcode
      • errcheck
      • goconst
      • goimports
      • gosimple
      • govet
      • ineffassign
      • staticcheck
      • structcheck
      • typecheck
      • unused
      • varcheck
  • Don’t print, use logger

Ref