• Stars
    star
    2,784
  • Rank 16,367 (Top 0.4 %)
  • Language
    Go
  • License
    The Unlicense
  • Created about 7 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

List of advice and tricks for Go ʕ◔ϖ◔ʔ

Go-advice

(Some of advices are implemented in go-critic)

Contents

Go Proverbs

  • Don't communicate by sharing memory, share memory by communicating.
  • Concurrency is not parallelism.
  • Channels orchestrate; mutexes serialize.
  • The bigger the interface, the weaker the abstraction.
  • Make the zero value useful.
  • interface{} says nothing.
  • Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.
  • A little copying is better than a little dependency.
  • Syscall must always be guarded with build tags.
  • Cgo must always be guarded with build tags.
  • Cgo is not Go.
  • With the unsafe package there are no guarantees.
  • Clear is better than clever.
  • Reflection is never clear.
  • Errors are values.
  • Don't just check errors, handle them gracefully.
  • Design the architecture, name the components, document the details.
  • Documentation is for users.
  • Don't panic.

Author: Rob Pike See more: https://go-proverbs.github.io/

The Zen of Go

  • Each package fulfils a single purpose
  • Handle errors explicitly
  • Return early rather than nesting deeply
  • Leave concurrency to the caller
  • Before you launch a goroutine, know when it will stop
  • Avoid package level state
  • Simplicity matters
  • Write tests to lock in the behaviour of your package’s API
  • If you think it’s slow, first prove it with a benchmark
  • Moderation is a virtue
  • Maintainability counts

Author: Dave Cheney See more: https://the-zen-of-go.netlify.com/

Code

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

Multiple if-else statements can be collapsed into a switch

// NOT BAD
if foo() {
	// ...
} else if bar == baz {
	// ...
} else {
	// ...
}

// BETTER
switch {
case foo():
	// ...
case bar == baz:
	// ...
default:
	// ...
}

To pass a signal prefer 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.
}

Prefer 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

Use time.Duration instead of int64 + variable name

// BAD
var delayMillis int64 = 15000

// GOOD
var delay time.Duration = 15 * time.Second

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

defer func() {
	err := ocp.Close()
	if err != nil {
		rerr = err
	}
}()
  • don't use checkErr function which panics or does os.Exit
  • use panic only in very specific situations, you have to handle error
  • don't use alias for enums 'cause this breaks type safety
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)
}
  • if you're going to omit returning params, do it explicitly
    • so prefer this _ = f() to this f()
  • the short form for slice initialization is a := []T{}
  • iterate over array or slice using range loop
    • instead of for i := 3; i < 7; i++ {...} prefer for _, c := range a[3:7] {...}
  • use backquote(`) for multiline strings
  • skip unused param with _
func f(a int, _ string) {}
  • If you are comparing timestamps, use time.Before or time.After. Don't use time.Sub to get a duration and then check its value.
  • always pass context as a first param to a func with a ctx name
  • few params of the same type can be defined in a short way
func 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
  • do not compare enum types with <, >, <= and >=
    • use explicit values, don't do this:
value := reflect.ValueOf(object)
kind := value.Kind()
if kind >= reflect.Chan && kind <= reflect.Slice {
	// ...
}
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
}
  • wrap errors with fmt.Errorf
    • so: fmt.Errorf("additional message to a given error: %w", err)
  • be careful with range in Go:
  • reading nonexistent key from map will not panic
    • value := map["no_key"] will be zero value
    • value, ok := map["no_key"] is much better
  • do not use raw params for file operation
    • instead of an octal parameter like os.MkdirAll(root, 0700)
    • use predefined constants of this type os.FileMode
  • don't forget to specify a type for iota
const (
	_ = iota
	testvar // will be int
)

vs

type myType int
const (
	_ myType = iota
	testvar // will be myType
)

Don’t use 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.

Don't depend on the evaluation order, especially in a return statement.

// BAD
return res, json.Unmarshal(b, &res)

// GOOD
err := json.Unmarshal(b, &res)
return res, err

To prevent unkeyed literals add _ 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.

To prevent structs comparison add an empty field of func type

type Point struct {
	_ [0]func()	// unexported, zero-width non-comparable field
	X, Y float64
}

Prefer http.HandlerFunc over http.Handler

To use the 1st one you just need a func, for the 2nd you need a type.

Move defer to the top

This improves code readability and makes clear what will be invoked at the end of a function.

JavaScript parses integers as floats and your int64 might overflow.

Use json:"id,string" instead.

type Request struct {
	ID int64 `json:"id,string"`
}

Concurrency

  • best candidate to make something once in a thread-safe way is sync.Once
    • don't use flags, mutexes, channels or atomics
  • to block forever use select{}, omit channels, waiting for a signal
  • don't close in-channel, this is a responsibility of it's creator
    • writing to a closed channel will cause a panic
  • func NewSource(seed int64) Source in math/rand is not concurrency-safe. The default lockedSource is concurrency-safe, see issue: golang/go#3611
  • when you need an atomic value of a custom type use atomic.Value

Performance

  • do not omit defer
    • 200ns speedup is negligible in most cases
  • always close http body aka defer r.Body.Close()
    • unless you need leaked goroutine
  • filtering without allocating
b := a[:0]
for _, x := range a {
	if f(x) {
		b = append(b, x)
	}
}

To help compiler to remove bound checks see this pattern _ = b[7]

  • time.Time has pointer field time.Location and this is bad for go GC
    • it's relevant only for big number of time.Time, use timestamp instead
  • prefer regexp.MustCompile instead of regexp.Compile
    • in most cases your regex is immutable, so init it in func init
  • do not overuse fmt.Sprintf in your hot path. It is costly due to maintaining the buffer pool and dynamic dispatches for interfaces.
    • if you are doing fmt.Sprintf("%s%s", var1, var2), consider simple string concatenation.
    • if you are doing fmt.Sprintf("%x", var), consider using hex.EncodeToString or strconv.FormatInt(var, 16)
  • always discard body e.g. io.Copy(ioutil.Discard, resp.Body) if you don't use it
    • HTTP client's Transport will not reuse connections unless the body is read to completion and closed
res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
defer res.Body.Close()
  • don't use defer in a loop or you'll get a small memory leak
    • 'cause defers will grow your stack without the reason
  • don't forget to stop ticker, unless you need a leaked channel
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
}
// 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)
}
  • for fastest atomic swap you might use this m := (*map[int]int)(atomic.LoadPointer(&ptr))
  • use buffered I/O if you do many sequential reads or writes
    • to reduce number of syscalls
  • there are 2 ways to clear a map:
    • reuse map memory
for k := range m {
	delete(m, k)
}
  • allocate new
m = make(map[int]int)

Modules

Build

Testing

  • prefer package_test name for tests, rather than package
  • go test -short allows to reduce set of tests to be runned
func TestSomething(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}
}
  • skip test depending on architecture
if runtime.GOARM == "arm" {
	t.Skip("this doesn't work under ARM")
}

Tools

  • quick replace 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
  • for fast benchmark comparison we've a benchstat tool
  • go-critic linter enforces several advices from this document
  • go mod why -m <module> tells us why a particular module is in the go.mod file
  • GOGC=off go build ... should speed up your builds source
  • The memory profiler records one allocation every 512Kbytes. You can increase the rate via the GODEBUG environment variable to see more details in your profile.

Misc

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])
	}
}()
  • check interface implementation during compilation
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
  • to get call stack we've runtime.Caller https://golang.org/pkg/runtime/#Caller
  • to marshal arbitrary JSON you can marshal to map[string]interface{}{}
  • configure your CDPATH so you can do cd github.com/golang/go from any directore
    • add this line to your bashrc(or analogue) export CDPATH=$CDPATH:$GOPATH/src
  • simple random element from a slice
    • []string{"one", "two", "three"}[rand.Intn(3)]

More Repositories

1

awesome-manifesto

Utopian developer's manifesto
315
star
2

awesome-load-balancing

A curated list of awesome load balancers and proxies. Software, libraries, posts, talks.
122
star
3

docker-compose-collection

Collection of popular docker-compose files for a quick access
114
star
4

sabotage

Collection of dirty hacks in Go
Go
65
star
5

interview-manifesto

34
star
6

awesome-oneliners

Copy-paste friendly Bash one liners
22
star
7

golds

Go data structures
Go
14
star
8

wabservar

Go
10
star
9

whatcango

Go
6
star
10

awesome-go-author

Collection of best practices, thoughts and practical advice for Go libraries authors and maintainers.
6
star
11

funf

Go playground, small snippets, experiments, fun.
Go
6
star
12

dsvreader

Go delimiter-separated values reader, ARCHIVED, see https://github.com/cristalhq/dsvreader
Go
5
star
13

talks

Talks
Go
5
star
14

scs

Go syscall sockets
Go
4
star
15

impguard

4
star
16

bc

Blockchain with websockets and BoltDB/BuntDB storages
Go
4
star
17

astp

ARCHIVED, SEE: https://github.com/go-toolsmith/astp Collection of Go AST predicates
Go
3
star
18

go-snippets

Code snippets of Go ʕ◔ϖ◔ʔ
3
star
19

benches

Me throwing my benchs, nothing else (╯°□°)╯︵ ┻━┻
Go
3
star
20

astify

CHAOTIC EVIL PRE-ALPHA WIP
Go
3
star
21

cristaloleg

Oleg Kovalov profile and website comments
3
star
22

slowio

Go
2
star
23

go-act

Abstract channel type
Go
2
star
24

algogems

Algorithms and data structures
C++
2
star
25

flache

Yet another cache
Go
2
star
26

caches

Go
2
star
27

regexpgen

Regexp generator
Go
2
star
28

go-awesome-talks

List of talks about the Go programming language
2
star
29

go-gen-syncmap

Go
2
star
30

fuzzqueens

How to fuzz wrong
Go
2
star
31

wsecho

Go
2
star
32

olegk

Oleg Kovalov Homepage
CSS
2
star
33

sudoku

Go
1
star
34

go-rafting

Go
1
star
35

as

Go
1
star
36

dotfiles

Shell
1
star
37

rand

Go
1
star
38

bigm

Go
1
star
39

cristaloleg.github.io

Web-site for https://cristaloleg.github.io
HTML
1
star
40

flearand

Go
1
star
41

shrtl

Go
1
star
42

wsps

Go
1
star
43

interval

Go interval arithmetic
Go
1
star
44

go-init

Initial git repo for Go
Makefile
1
star
45

blog

1
star
46

gql2yml

Convert your GraphQL schema to YAML and JSON
Go
1
star
47

public-bugs

Go
1
star
48

is

Go
1
star
49

goal

Go algorithms
Go
1
star
50

ease

easy serving
Dockerfile
1
star
51

projecteuler

Solved problems from projecteuler.net
Go
1
star
52

rxgo

Go
1
star
53

dockedocus

Prebuild Docusaurus
Dockerfile
1
star
54

gopds

Go persistent data structures
Go
1
star