@zhongjianxin
        
        2020-02-11T10:20:27.000000Z
        字数 30943
        阅读 900
    AFS-Golang
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.
~~~
| Bad | Good | 
|---|---|
  | 
  | 
type S struct {data string}func (s S) Read() string {return s.data}func (s *S) Write(str string) {s.data = str}sVals := map[int]S{1: {"A"}}// You can only call Read using a valuesVals[1].Read()// This will not compile:// sVals[1].Write("test")sPtrs := map[int]*S{1: {"A"}}// You can call both Read and Write using a pointersPtrs[1].Read()sPtrs[1].Write("test")
type F interface {f()}type S1 struct{}func (s S1) f() {}type S2 struct{}func (s *S2) f() {}s1Val := S1{}s1Ptr := &S1{}s2Val := S2{}s2Ptr := &S2{}var i Fi = s1Vali = s1Ptri = s2Ptr// The following doesn't compile, since s2Val is a value, and there is no value receiver for f.// i = s2Val
| Bad | Good | 
|---|---|
  | 
  | 
  | 
  | 
| Embed for private types or types that need to implement the Mutex interface. | For exported types, use a private field. | 
| Bad | Good | 
|---|---|
  | 
  | 
Similarly, be wary of user modifications to maps or slices exposing internal 
state.
| Bad | Good | 
|---|---|
  | 
  | 
Use defer to clean up resources such as files and locks.
| Bad | Good | 
|---|---|
  | 
  | 
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.
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.
| Bad | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
There are cases where using the zero value makes sense, for example when the 
zero value case is the desirable default behavior.
type LogOutput intconst (LogToStdout LogOutput = iotaLogToFileLogToRemote)// LogToStdout=0, LogToFile=1, LogToRemote=2
"time" to handle timeTime is complicated. Incorrect assumptions often made about time include the 
following.
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.
time.Time for instants of timeUse [time.Time] when dealing with instants of time, and the methods on 
time.Time when comparing, adding, or subtracting time.
| Bad | Good | 
|---|---|
  | 
  | 
time.Duration for periods of timeUse [time.Duration] when dealing with periods of time.
| Bad | Good | 
|---|---|
  | 
  | 
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].
newDay := t.AddDate(0 /* years */, 0, /* months */, 1 /* days */)maybeNewDay := t.Add(24 * time.Hour)
time.Time and time.Duration with external systemsUse time.Duration and time.Time in interactions with external systems when 
possible. For example:
flag] supports time.Duration via time.ParseDuration]encoding/json] supports encoding time.Time as an RFC 3339 UnmarshalJSON method]database/sql] supports converting DATETIME or TIMESTAMP columns time.Time and back if the underlying driver supports itgopkg.in/yaml.v2] supports time.Time as an RFC 3339 string, and time.Duration via [time.ParseDuration].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.
| Bad | Good | 
|---|---|
  | 
  | 
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.
There are various options for declaring errors:
errors.New] for errors with simple static stringsfmt.Errorf] for formatted error stringsError() method"pkg/errors".Wrap]When returning errors, consider the following to determine the best choice:
errors.New] Error() method.fmt.Errorf] is okay.If the client needs to detect the error, and you have created a simple error 
using [errors.New], use a var for the error.
| Bad | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
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.
// package footype errNotFound struct {file string}func (e errNotFound) Error() string {return fmt.Sprintf("file %q not found", e.file)}func IsNotFoundError(err error) bool {_, ok := err.(errNotFound)return ok}func Open(file string) error {return errNotFound{file: file}}// package barif err := foo.Open("foo"); err != nil {if foo.IsNotFoundError(err) {// handle} else {panic("unknown error")}}
There are three main options for propagating errors if a call fails:
"pkg/errors".Wrap] so that the error message provides "pkg/errors".Cause] can be used to extract the original fmt.Errorf] if the callers do not need to detect or handle that 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:
| Bad | Good | 
|---|---|
  | 
  | 
  | 
  | 
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.
The single return value form of a type assertion will panic on an incorrect 
type. Therefore, always use the "comma ok" idiom.
| Bad | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
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.
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.
| Bad | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
Avoid mutating global variables, instead opting for dependency injection. 
This applies to function pointers as well as other kinds of values.
| Bad | Good | 
|---|---|
  | 
  | 
  | 
  | 
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.
type AbstractList struct {}// Add adds an entity to the list.func (l *AbstractList) Add(e Entity) {// ...}// Remove removes an entity from the list.func (l *AbstractList) Remove(e Entity) {// ...}
| Bad | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
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-specific guidelines apply only to the hot path.
When converting primitives to/from strings, strconv is faster than 
fmt.
| Bad | Good | 
|---|---|
  | 
  | 
  | 
  | 
Do not create byte slices from a fixed string repeatedly. Instead, perform the 
conversion once and capture the result.
| Bad | Good | 
|---|---|
  | 
  | 
  | 
  | 
Where possible, provide capacity hints when initializing 
maps with make().
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.
| Bad | Good | 
|---|---|
  | 
  | 
| `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. | 
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.
Go supports grouping similar declarations.
| Bad | Good | 
|---|---|
  | 
  | 
This also applies to constants, variables, and type declarations.
| Bad | Good | 
|---|---|
  | 
  | 
Only group related declarations. Do not group declarations that are unrelated.
| Bad | Good | 
|---|---|
  | 
  | 
Groups are not limited in where they can be used. For example, you can use them 
inside of functions.
| Bad | Good | 
|---|---|
  | 
  | 
There should be two import groups:
This is the grouping applied by goimports by default.
| Bad | Good | 
|---|---|
  | 
  | 
When naming packages, choose a name that is:
net/url, not net/urls.See also Package Names and Style guideline for Go packages.
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 must be used if the package name does not match the last 
element of the import path.
import ("net/http"client "example.com/client-go"trace "example.com/trace/v2")
In all other scenarios, import aliases should be avoided unless there is a 
direct conflict between imports.
| Bad | Good | 
|---|---|
  | 
  | 
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 | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
If a variable is set in both branches of an if, it can be replaced with a 
single if.
| Bad | Good | 
|---|---|
  | 
  | 
At the top level, use the standard var keyword. Do not specify the type, 
unless it is not the same type as the expression.
| Bad | Good | 
|---|---|
  | 
  | 
Specify the type if the type of the expression does not match the desired type 
exactly.
type myError struct{}func (myError) Error() string { return "error" }func F() myError { return myError{} }var _e error = F()// F returns an object of type myError but we want error.
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.
| Bad | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
You should almost always specify field names when initializing structs. This is 
now enforced by [go vet].
| Bad | Good | 
|---|---|
  | 
  | 
Exception: Field names may be omitted in test tables when there are 3 or 
fewer fields.
tests := []struct{op Operationwant string}{{Add, "add"},{Subtract, "subtract"},}
Short variable declarations (:=) should be used if a variable is being set to 
some value explicitly.
| Bad | Good | 
|---|---|
  | 
  | 
However, there are cases where the default value is clearer when the var 
keyword is used. Declaring Empty Slices, for example.
| Bad | Good | 
|---|---|
  | 
  | 
nil is a valid slice of length 0. This means that,
You should not return a slice of length zero explicitly. Return nil 
instead.
| Bad | Good | 
|---|---|
  | 
  | 
To check if a slice is empty, always use len(s) == 0. Do not check for 
nil.
| Bad | Good | 
|---|---|
  | 
  | 
The zero value (a slice declared with var) is usable immediately without 
make().
| Bad | Good | 
|---|---|
  | 
  | 
Where possible, reduce scope of variables. Do not reduce the scope if it 
conflicts with Reduce Nesting.
| Bad | Good | 
|---|---|
  | 
  | 
If you need a result of a function call outside of the if, then you should not 
try to reduce the scope.
| Bad | Good | 
|---|---|
  | 
  | 
Naked parameters in function calls can hurt readability. Add C-style comments 
(/* ... */) for parameter names when their meaning is not obvious.
| Bad | Good | 
|---|---|
  | 
  | 
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 intconst (UnknownRegion Region = iotaLocal)type Status intconst (StatusReady = iota + 1StatusDone// Maybe we will have a StatusInProgress in the future.)func printInfo(name string, region Region, status Status)
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.
| Bad | Good | 
|---|---|
  | 
  | 
Use &T{} instead of new(T) when initializing struct references so that it 
is consistent with the struct initialization.
| Bad | Good | 
|---|---|
  | 
  | 
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.
| Bad | Good | 
|---|---|
  | 
  | 
| 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.
| Bad | Good | 
|---|---|
  | 
  | 
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).
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.
| Bad | Good | 
|---|---|
  | 
  | 
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.
Use table-driven tests with subtests to avoid duplicating code when the core 
test logic is repetitive.
| Bad | Good | 
|---|---|
  | 
  | 
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.
tests := []struct{give stringwantHost stringwantPort string}{// ...}for _, tt := range tests {// ...}
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.
// package dbtype Option interface {// ...}func WithCache(c bool) Option {// ...}func WithLogger(log *zap.Logger) Option {// ...}// Open creates a connection.func Open(addr string,opts ...Option,) (*Connection, error) {// ...}
db.Open(addr, db.DefaultCache, zap.NewNop())db.Open(addr, db.DefaultCache, log)db.Open(addr, false /* cache */, zap.NewNop())db.Open(addr, false /* cache */, log)
db.Open(addr)db.Open(addr, db.WithLogger(log))db.Open(addr, db.WithCache(false))db.Open(addr,db.WithCache(false),db.WithLogger(log),)
| Bad | Good | 
|---|---|
  | 
Our suggested way of implementing this pattern is with an Option interface 
that holds an unexported method, recording options on an unexported options 
struct.
type options struct {cache boollogger *zap.Logger}type Option interface {apply(*options)}type cacheOption boolfunc (c cacheOption) apply(opts *options) {opts.cache = bool(c)}func WithCache(c bool) Option {return cacheOption(c)}type loggerOption struct {Log *zap.Logger}func (l loggerOption) apply(opts *options) {opts.logger = l.Log}func WithLogger(log *zap.Logger) Option {return loggerOption{Log: log}}// Open creates a connection.func Open(addr string,opts ...Option,) (*Connection, error) {options := options{cache: defaultCache,logger: zap.NewNop(),}for _, o := range opts {o.apply(&options)}// ...}
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,