List of advice and tricks for Go ʕ◔ϖ◔ʔ
(Some of advices are implemented in go-critic)
interface{}
says nothing.Author: Rob Pike See more: https://go-proverbs.github.io/
Author: Dave Cheney See more: https://the-zen-of-go.netlify.com/
go fmt
your code.Community uses the official Go format, do not reinvent the wheel.
Try to reduce code entropy. This will help everyone to make code easy to read.
// NOT BAD
if foo() {
// ...
} else if bar == baz {
// ...
} else {
// ...
}
// BETTER
switch {
case foo():
// ...
case bar == baz:
// ...
default:
// ...
}
chan struct{}
instead of chan bool
.When you see a definition of chan bool
in a structure, sometimes it's not that easy to understand how this value will be used, example:
type Service struct {
deleteCh chan bool // what does this bool mean?
}
But we can make it more clear by changing it to chan struct{}
which explicitly says: we do not care about value (it's always a struct{}
), we care about an event that might occur, example:
type Service struct {
deleteCh chan struct{} // ok, if event than delete something.
}
30 * time.Second
instead of time.Duration(30) * time.Second
You don't need to wrap untyped const in a type, compiler will figure it out. Also prefer to move const to the first place:
// BAD
delay := time.Second * 60 * 24 * 60
// VERY BAD
delay := 60 * time.Second * 60 * 24
// GOOD
delay := 24 * 60 * 60 * time.Second
// EVEN BETTER
delay := 24 * time.Hour
time.Duration
instead of int64
+ variable name// BAD
var delayMillis int64 = 15000
// GOOD
var delay time.Duration = 15 * time.Second
const
declarations by type and var
by logic and/or type// BAD
const (
foo = 1
bar = 2
message = "warn message"
)
// MOSTLY BAD
const foo = 1
const bar = 2
const message = "warn message"
// GOOD
const (
foo = 1
bar = 2
)
const message = "warn message"
This pattern works for var
too.
Stringer
interface for integers const values
defer func() {
err := ocp.Close()
if err != nil {
rerr = err
}
}()
checkErr
function which panics or does os.Exit
package main
type Status = int
type Format = int // remove `=` to have type safety
const A Status = 1
const B Format = 1
func main() {
println(A == B)
}
_ = f()
to this f()
a := []T{}
for i := 3; i < 7; i++ {...}
prefer for _, c := range a[3:7] {...}
func f(a int, _ string) {}
time.Before
or time.After
. Don't use time.Sub
to get a duration and then check its value.ctx
namefunc f(a int, b int, s string, p string)
func f(a, b int, s, p string)
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
// Output:
// [] 0 0
// nil!
var a []string
b := []string{}
fmt.Println(reflect.DeepEqual(a, []string{}))
fmt.Println(reflect.DeepEqual(b, []string{}))
// Output:
// false
// true
<
, >
, <=
and >=
value := reflect.ValueOf(object)
kind := value.Kind()
if kind >= reflect.Chan && kind <= reflect.Slice {
// ...
}
%+v
to print data with sufficient detailsstruct{}
, see issue: https://github.com/golang/go/issues/23440
func f1() {
var a, b struct{}
print(&a, "\n", &b, "\n") // Prints same address
fmt.Println(&a == &b) // Comparison returns false
}
func f2() {
var a, b struct{}
fmt.Printf("%p\n%p\n", &a, &b) // Again, same address
fmt.Println(&a == &b) // ...but the comparison returns true
}
fmt.Errorf
fmt.Errorf("additional message to a given error: %w", err)
range
in Go:
for i := range a
and for i, v := range &a
doesn't make a copy of a
for i, v := range a
doesvalue := map["no_key"]
will be zero valuevalue, ok := map["no_key"]
is much betteros.MkdirAll(root, 0700)
os.FileMode
iota
const (
_ = iota
testvar // will be int
)
vs
type myType int
const (
_ myType = iota
testvar // will be myType
)
encoding/gob
on structs you don’t own.At some point structure may change and you might miss this. As a result this might cause a hard to find bug.
// BAD
return res, json.Unmarshal(b, &res)
// GOOD
err := json.Unmarshal(b, &res)
return res, err
_ struct{}
field:type Point struct {
X, Y float64
_ struct{} // to prevent unkeyed literals
}
For Point{X: 1, Y: 1}
everything will be fine, but for Point{1,1}
you will get a compile error:
./file.go:1:11: too few values in Point literal
There is a check in go vet
command for this, there is no enough arguments to add _ struct{}
in all your structs.
func
typetype Point struct {
_ [0]func() // unexported, zero-width non-comparable field
X, Y float64
}
http.HandlerFunc
over http.Handler
To use the 1st one you just need a func, for the 2nd you need a type.
defer
to the topThis improves code readability and makes clear what will be invoked at the end of a function.
Use json:"id,string"
instead.
type Request struct {
ID int64 `json:"id,string"`
}
sync.Once
select{}
, omit channels, waiting for a signalfunc NewSource(seed int64) Source
in math/rand
is not concurrency-safe. The default lockedSource
is concurrency-safe, see issue: https://github.com/golang/go/issues/3611
defer
defer r.Body.Close()
b := a[:0]
for _, x := range a {
if f(x) {
b = append(b, x)
}
}
_ = b[7]
time.Time
has pointer field time.Location
and this is bad for go GC
time.Time
, use timestamp insteadregexp.MustCompile
instead of regexp.Compile
func init
fmt.Sprintf
in your hot path. It is costly due to maintaining the buffer pool and dynamic dispatches for interfaces.
fmt.Sprintf("%s%s", var1, var2)
, consider simple string concatenation.fmt.Sprintf("%x", var)
, consider using hex.EncodeToString
or strconv.FormatInt(var, 16)
io.Copy(ioutil.Discard, resp.Body)
if you don't use it
res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
defer res.Body.Close()
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
func (entry Entry) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString("{")
first := true
for key, value := range entry {
jsonValue, err := json.Marshal(value)
if err != nil {
return nil, err
}
if !first {
buffer.WriteString(",")
}
first = false
buffer.WriteString(key + ":" + string(jsonValue))
}
buffer.WriteString("}")
return buffer.Bytes(), nil
}
sync.Map
isn't a silver bullet, do not use it without a strong reasons
sync.Pool
allocates memory
// noescape hides a pointer from escape analysis. noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input. noescape is inlined and currently
// compiles down to zero instructions.
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
m := (*map[int]int)(atomic.LoadPointer(&ptr))
for k := range m {
delete(m, k)
}
m = make(map[int]int)
go.mod
(and go.sum
) is up to date in CI
https://blog.urth.org/2019/08/13/testing-go-mod-tidiness-in-ci/
go build -ldflags="-s -w" ...
// +build integration
and run them with go test -v --tags integration .
CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latest
go format
on CI and compare diff
travis 1
diff -u <(echo -n) <(gofmt -d .)
package_test
name for tests, rather than package
go test -short
allows to reduce set of tests to be runnedfunc TestSomething(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
}
if runtime.GOARM == "arm" {
t.Skip("this doesn't work under ARM")
}
testing.AllocsPerRun
go test -test.bench=. -count=20
gofmt -w -l -r "panic(err) -> log.Error(err)" .
go list
allows to find all direct and transitive dependencies
go list -f '{{ .Imports }}' package
go list -f '{{ .Deps }}' package
benchstat
tool
go mod why -m <module>
tells us why a particular module is in the go.mod
fileGOGC=off go build ...
should speed up your builds source
GODEBUG
environment variable to see more details in your profile.
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGQUIT)
buf := make([]byte, 1<<20)
for {
<-sigs
stacklen := runtime.Stack(buf, true)
log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen])
}
}()
var _ io.Reader = (*MyFastReader)(nil)
var hits struct {
sync.Mutex
n int
}
hits.Lock()
hits.n++
hits.Unlock()
httputil.DumpRequest
is very useful thing, don't create your own
runtime.Caller
https://golang.org/pkg/runtime/#Caller
map[string]interface{}{}
CDPATH
so you can do cd github.com/golang/go
from any directore
bashrc
(or analogue) export CDPATH=$CDPATH:$GOPATH/src
[]string{"one", "two", "three"}[rand.Intn(3)]