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 thanonce.DoOrWaitUntilDone(setup)
- eg:
- package names should be short, just one lowercase word, no underscores (
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
- If you only need the subscript of slice or the key of map, then you only need to do:
- 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)
- If the type assertion of
- 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
[root@liqiang.io]# cat runutil.go
...
// CloseWithErrCapture runs function and on error return error by argument including the given error (usually
// from caller function).
func CloseWithErrCapture(err *error, closer io.Closer, format string, a . . interface{}) {
merr := errutil.MultiError{}
merr.Add(*err)
merr.Add(errors.Wrapf(closer.Close(), format, a ...))
*err = merr.Err()
}
- 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