{"id":13600889,"url":"https://github.com/cristaloleg/go-advice","last_synced_at":"2025-09-27T08:30:41.047Z","repository":{"id":37313635,"uuid":"105425973","full_name":"cristaloleg/go-advice","owner":"cristaloleg","description":"List of advice and tricks for Go  ʕ◔ϖ◔ʔ","archived":false,"fork":false,"pushed_at":"2022-10-08T07:07:37.000Z","size":186,"stargazers_count":3125,"open_issues_count":2,"forks_count":201,"subscribers_count":76,"default_branch":"master","last_synced_at":"2025-01-11T18:44:37.923Z","etag":null,"topics":["advice","awesome","experience","go","golang","guide","guidelines"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cristaloleg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-01T07:23:09.000Z","updated_at":"2025-01-11T14:17:18.000Z","dependencies_parsed_at":"2022-08-08T20:01:03.641Z","dependency_job_id":null,"html_url":"https://github.com/cristaloleg/go-advice","commit_stats":null,"previous_names":["cristaloleg/go-advices"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristaloleg%2Fgo-advice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristaloleg%2Fgo-advice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristaloleg%2Fgo-advice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristaloleg%2Fgo-advice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cristaloleg","download_url":"https://codeload.github.com/cristaloleg/go-advice/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234410561,"owners_count":18828235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["advice","awesome","experience","go","golang","guide","guidelines"],"created_at":"2024-08-01T18:00:50.481Z","updated_at":"2025-09-27T08:30:41.039Z","avatar_url":"https://github.com/cristaloleg.png","language":"Go","funding_links":[],"categories":["Go","其他","Repositories"],"sub_categories":[],"readme":"# Go-advice\n\n(Some of advices are implemented in [go-critic](https://github.com/go-critic/go-critic))\n\n- [中文版](./README_ZH.md)\n- [한국어](./README_KR.md)\n\n## Contents\n\n- [Go Proverbs](#go-proverbs)\n- [The Zen of Go](#the-zen-of-go)\n- [Code](#code)\n- [Concurrency](#concurrency)\n- [Performance](#performance)\n- [Modules](#modules)\n- [Build](#build)\n- [Testing](#testing)\n- [Tools](#tools)\n- [Misc](#misc)\n\n### Go Proverbs\n\n- Don't communicate by sharing memory, share memory by communicating.\n- Concurrency is not parallelism.\n- Channels orchestrate; mutexes serialize.\n- The bigger the interface, the weaker the abstraction.\n- Make the zero value useful.\n- `interface{}` says nothing.\n- Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.\n- A little copying is better than a little dependency.\n- Syscall must always be guarded with build tags.\n- Cgo must always be guarded with build tags.\n- Cgo is not Go.\n- With the unsafe package there are no guarantees.\n- Clear is better than clever.\n- Reflection is never clear.\n- Errors are values.\n- Don't just check errors, handle them gracefully.\n- Design the architecture, name the components, document the details.\n- Documentation is for users.\n- Don't panic.\n\nAuthor: Rob Pike\nSee more: https://go-proverbs.github.io/\n\n### The Zen of Go\n\n- Each package fulfils a single purpose\n- Handle errors explicitly\n- Return early rather than nesting deeply\n- Leave concurrency to the caller\n- Before you launch a goroutine, know when it will stop\n- Avoid package level state\n- Simplicity matters\n- Write tests to lock in the behaviour of your package’s API\n- If you think it’s slow, first prove it with a benchmark\n- Moderation is a virtue\n- Maintainability counts\n\nAuthor: Dave Cheney\nSee more: [https://the-zen-of-go.netlify.com/](https://the-zen-of-go.netlify.app/)\n\n### Code\n\n#### Always `go fmt` your code.\n\nCommunity uses the official Go format, do not reinvent the wheel.\n\nTry to reduce code entropy. This will help everyone to make code easy to read.\n\n#### Multiple if-else statements can be collapsed into a switch\n\n```go\n// NOT BAD\nif foo() {\n\t// ...\n} else if bar == baz {\n\t// ...\n} else {\n\t// ...\n}\n\n// BETTER\nswitch {\ncase foo():\n\t// ...\ncase bar == baz:\n\t// ...\ndefault:\n\t// ...\n}\n```\n\n#### To pass a signal prefer `chan struct{}` instead of `chan bool`.\n\nWhen 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:\n```go\ntype Service struct {\n\tdeleteCh chan bool // what does this bool mean? \n}\n```\n\nBut 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:\n```go\ntype Service struct {\n\tdeleteCh chan struct{} // ok, if event than delete something.\n}\n```\n\n#### Prefer `30 * time.Second` instead of `time.Duration(30) * time.Second`\n\nYou don't need to wrap untyped const in a type, compiler will figure it out. Also prefer to move const to the first place:\n```go\n// BAD\ndelay := time.Second * 60 * 24 * 60\n\n// VERY BAD\ndelay := 60 * time.Second * 60 * 24\n\n// GOOD\ndelay := 24 * 60 * 60 * time.Second\n\n// EVEN BETTER\ndelay := 24 * time.Hour\n```\n\n#### Use `time.Duration` instead of `int64` + variable name\n\n```go\n// BAD\nvar delayMillis int64 = 15000\n\n// GOOD\nvar delay time.Duration = 15 * time.Second\n```\n\n#### Group `const` declarations by type and `var` by logic and/or type\n\n```go\n// BAD\nconst (\n\tfoo     = 1\n\tbar     = 2\n\tmessage = \"warn message\"\n)\n\n// MOSTLY BAD\nconst foo = 1\nconst bar = 2\nconst message = \"warn message\"\n\n// GOOD\nconst (\n\tfoo = 1\n\tbar = 2\n)\n\nconst message = \"warn message\"\n```\n\nThis pattern works for `var` too.\n\n- [ ] every blocking or IO function call should be cancelable or at least timeoutable\n- [ ] implement `Stringer` interface for integers const values\n  - https://godoc.org/golang.org/x/tools/cmd/stringer\n- [ ] check your defer's error\n\n```go\ndefer func() {\n\terr := ocp.Close()\n\tif err != nil {\n\t\trerr = err\n\t}\n}()\n```\n\n- [ ] don't use `checkErr` function which panics or does `os.Exit`\n- [ ] use panic only in very specific situations, you have to handle error\n- [ ] don't use alias for enums 'cause this breaks type safety\n  - https://play.golang.org/p/MGbeDwtXN3\n\n```go\npackage main\n\ntype Status = int\ntype Format = int // remove `=` to have type safety\n\nconst A Status = 1\nconst B Format = 1\n\nfunc main() {\n\tprintln(A == B)\n}\n```\n\n- [ ] if you're going to omit returning params, do it explicitly\n  - so prefer this ` _ = f()` to this `f()`\n- [ ] the short form for slice initialization is `a := []T{}`\n- [ ] iterate over array or slice using range loop\n  -  instead of `for i := 3; i \u003c 7; i++ {...}` prefer `for _, c := range a[3:7] {...}`\n- [ ] use backquote(\\`) for multiline strings\n- [ ] skip unused param with _\n\n```go\nfunc f(a int, _ string) {}\n```\n\n- [ ] 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.\n- [ ] always pass context as a first param to a func with a `ctx` name\n- [ ] few params of the same type can be defined in a short way\n\n```go\nfunc f(a int, b int, s string, p string)\n```\n\n```go\nfunc f(a, b int, s, p string)\n```\n\n- [ ] the zero value of a slice is nil\n  - https://play.golang.org/p/pNT0d_Bunq\n\n```go\nvar s []int\nfmt.Println(s, len(s), cap(s))\nif s == nil {\n\tfmt.Println(\"nil!\")\n}\n// Output:\n// [] 0 0\n// nil!\n```\n\n  - https://play.golang.org/p/meTInNyxtk\n\n```go\nvar a []string\nb := []string{}\n\nfmt.Println(reflect.DeepEqual(a, []string{}))\nfmt.Println(reflect.DeepEqual(b, []string{}))\n// Output:\n// false\n// true\n```\n\n- [ ] do not compare enum types with `\u003c`, `\u003e`, `\u003c=` and `\u003e=`\n  - use explicit values, don't do this:\n  \n```go\nvalue := reflect.ValueOf(object)\nkind := value.Kind()\nif kind \u003e= reflect.Chan \u0026\u0026 kind \u003c= reflect.Slice {\n\t// ...\n}\n```\n\n- [ ] use `%+v` to print data with sufficient details\n- [ ] be careful with empty struct `struct{}`, see issue: https://github.com/golang/go/issues/23440\n  - more: https://play.golang.org/p/9C0puRUstrP\n\n```go\nfunc f1() {\n\tvar a, b struct{}\n\tprint(\u0026a, \"\\n\", \u0026b, \"\\n\") // Prints same address\n\tfmt.Println(\u0026a == \u0026b)     // Comparison returns false\n}\n\nfunc f2() {\n\tvar a, b struct{}\n\tfmt.Printf(\"%p\\n%p\\n\", \u0026a, \u0026b) // Again, same address\n\tfmt.Println(\u0026a == \u0026b)          // ...but the comparison returns true\n}\n```\n\n- [ ] wrap errors with `fmt.Errorf`\n  - so: `fmt.Errorf(\"additional message to a given error: %w\", err)`\n- [ ] be careful with `range` in Go:\n  - `for i := range a` and `for i, v := range \u0026a` doesn't make a copy of `a`\n  - but `for i, v := range a` does\n  - more: https://play.golang.org/p/4b181zkB1O\n- [ ] reading nonexistent key from map will not panic\n  - `value := map[\"no_key\"]` will be zero value\n  - `value, ok := map[\"no_key\"]` is much better\n- [ ] do not use raw params for file operation\n  - instead of an octal parameter like `os.MkdirAll(root, 0700)`\n  - use predefined constants of this type `os.FileMode`\n- [ ] don't forget to specify a type for `iota`\n  - https://play.golang.org/p/mZZdMaI92cI\n\n```go\nconst (\n\t_ = iota\n\ttestvar // will be int\n)\n```\n\n  vs\n\n```go\ntype myType int\nconst (\n\t_ myType = iota\n\ttestvar // will be myType\n)\n```\n\n#### Don’t use `encoding/gob` on structs you don’t own.\n\nAt some point structure may change and you might miss this. As a result this might cause a hard to find bug.\n\n#### Don't depend on the evaluation order, especially in a return statement.\n\n```go\n// BAD\nreturn res, json.Unmarshal(b, \u0026res)\n\n// GOOD\nerr := json.Unmarshal(b, \u0026res)\nreturn res, err\n```\n\n#### To prevent unkeyed literals add `_ struct{}` field:\n\n```go\ntype Point struct {\n\tX, Y float64\n\t_    struct{} // to prevent unkeyed literals\n}\n```\n\nFor `Point{X: 1, Y: 1}` everything will be fine, but for `Point{1,1}` you will get a compile error:\n```\n./file.go:1:11: too few values in Point literal\n```\n\nThere is a check in `go vet` command for this, there is no enough arguments to add `_ struct{}` in all your structs.\n\n#### To prevent structs comparison add an empty field of `func` type\n\n```go\ntype Point struct {\n\t_ [0]func()\t// unexported, zero-width non-comparable field\n\tX, Y float64\n}\n```\n\n#### Prefer `http.HandlerFunc` over `http.Handler`\n\nTo use the 1st one you just need a func, for the 2nd you need a type.\n\n#### Move `defer` to the top\n\nThis improves code readability and makes clear what will be invoked at the end of a function.\n\n#### JavaScript parses integers as floats and your int64 might overflow.\n\nUse `json:\"id,string\"` instead.\n\n```go\ntype Request struct {\n\tID int64 `json:\"id,string\"`\n}\n```\n\n### Concurrency\n- [ ] best candidate to make something once in a thread-safe way is `sync.Once`\n  - don't use flags, mutexes, channels or atomics\n- [ ] to block forever use `select{}`, omit channels, waiting for a signal\n- [ ] don't close in-channel, this is a responsibility of it's creator\n  - writing to a closed channel will cause a panic\n- [ ] `func 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\n  - more: https://golang.org/pkg/math/rand/\n- [ ] when you need an atomic value of a custom type use [atomic.Value](https://godoc.org/sync/atomic#Value)\n\n### Performance\n- [ ] do not omit `defer`\n  - 200ns speedup is negligible in most cases\n- [ ] always close http body aka `defer r.Body.Close()`\n  - unless you need leaked goroutine\n- [ ] filtering without allocating\n\n```go\nb := a[:0]\nfor _, x := range a {\n\tif f(x) {\n\t\tb = append(b, x)\n\t}\n}\n```\n\n#### To help compiler to remove bound checks see this pattern `_ = b[7]`\n\n- [ ] `time.Time` has pointer field `time.Location` and this is bad for go GC\n  - it's relevant only for big number of `time.Time`, use timestamp instead\n- [ ] prefer `regexp.MustCompile` instead of `regexp.Compile`\n  - in most cases your regex is immutable, so init it in `func init`\n- [ ] do not overuse `fmt.Sprintf` in your hot path. It is costly due to maintaining the buffer pool and dynamic dispatches for interfaces.\n  - if you are doing `fmt.Sprintf(\"%s%s\", var1, var2)`, consider simple string concatenation.\n  - if you are doing `fmt.Sprintf(\"%x\", var)`, consider using `hex.EncodeToString` or `strconv.FormatInt(var, 16)`\n- [ ] always discard body e.g. `io.Copy(ioutil.Discard, resp.Body)` if you don't use it\n  - HTTP client's Transport will not reuse connections unless the body is read to completion and closed\n\n```go\nres, _ := client.Do(req)\nio.Copy(ioutil.Discard, res.Body)\ndefer res.Body.Close()\n```\n\n- [ ] don't use defer in a loop or you'll get a small memory leak\n  - 'cause defers will grow your stack without the reason\n- [ ] don't forget to stop ticker, unless you need a leaked channel\n\n```go\nticker := time.NewTicker(1 * time.Second)\ndefer ticker.Stop()\n```\n\n- [ ] use custom marshaler to speed up marshaling\n  - but before using it - profile! ex: https://play.golang.org/p/SEm9Hvsi0r\n\n```go\nfunc (entry Entry) MarshalJSON() ([]byte, error) {\n\tbuffer := bytes.NewBufferString(\"{\")\n\tfirst := true\n\tfor key, value := range entry {\n\t\tjsonValue, err := json.Marshal(value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !first {\n\t\t\tbuffer.WriteString(\",\")\n\t\t}\n\t\tfirst = false\n\t\tbuffer.WriteString(key + \":\" + string(jsonValue))\n\t}\n\tbuffer.WriteString(\"}\")\n\treturn buffer.Bytes(), nil\n}\n```\n\n- [ ] `sync.Map` isn't a silver bullet, do not use it without a strong reasons\n  - more: https://github.com/golang/go/blob/master/src/sync/map.go#L12\n- [ ] storing non-pointer values in `sync.Pool` allocates memory\n  - more: https://staticcheck.io/docs/checks#SA6002\n- [ ] to hide a pointer from escape analysis you might carefully(!!!) use this func:\n  - source: https://go-review.googlesource.com/c/go/+/86976\n\n```go\n// noescape hides a pointer from escape analysis.  noescape is\n// the identity function but escape analysis doesn't think the\n// output depends on the input. noescape is inlined and currently\n// compiles down to zero instructions.\nfunc noescape(p unsafe.Pointer) unsafe.Pointer {\n\tx := uintptr(p)\n\treturn unsafe.Pointer(x ^ 0)\n}\n```\n\n- [ ] for fastest atomic swap you might use this\n  `m := (*map[int]int)(atomic.LoadPointer(\u0026ptr))`\n- [ ] use buffered I/O if you do many sequential reads or writes\n  - to reduce number of syscalls\n- [ ] there are 2 ways to clear a map:\n  - reuse map memory\n\n```go\nfor k := range m {\n\tdelete(m, k)\n}\n```\n\n  - allocate new\n\n```go\nm = make(map[int]int)\n```\n\n### Modules\n- [ ] if you want to test that `go.mod` (and `go.sum`) is up to date in CI\n  https://blog.urth.org/2019/08/13/testing-go-mod-tidiness-in-ci/\n\n### Build\n- [ ] strip your binaries with this command `go build -ldflags=\"-s -w\" ...`\n- [ ] easy way to split test into different builds\n  - use `// +build integration` and run them with `go test -v --tags integration .`\n- [ ] tiniest Go docker image\n  - https://twitter.com/bbrodriges/status/873414658178396160\n  - `CGO_ENABLED=0 go build -ldflags=\"-s -w\" app.go \u0026\u0026 tar C app | docker import - myimage:latest`\n- [ ] run `go format` on CI and compare diff\n  - this will ensure that everything was generated and committed\n- [ ] to run Travis-CI with the latest Go use `travis 1`\n  - see more: https://github.com/travis-ci/travis-build/blob/master/public/version-aliases/go.json\n- [ ] check if there are mistakes in code formatting `diff -u \u003c(echo -n) \u003c(gofmt -d .)`\n\n### Testing\n- [ ] prefer `package_test` name for tests, rather than `package`\n- [ ] `go test -short` allows to reduce set of tests to be runned\n\n```go\nfunc TestSomething(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skipping test in short mode.\")\n\t}\n}\n```\n\n- [ ] skip test depending on architecture\n\n```go\nif runtime.GOARM == \"arm\" {\n\tt.Skip(\"this doesn't work under ARM\")\n}\n```\n\n- [ ] track your allocations with `testing.AllocsPerRun`\n  - https://godoc.org/testing#AllocsPerRun\n- [ ] run your benchmarks multiple times, to get rid of noise\n  - `go test -test.bench=. -count=20`\n\n### Tools\n- [ ] quick replace `gofmt -w -l -r \"panic(err) -\u003e log.Error(err)\" .`\n- [ ] `go list` allows to find all direct and transitive dependencies\n  - `go list -f '{{ .Imports }}' package`\n  - `go list -f '{{ .Deps }}' package`\n- [ ] for fast benchmark comparison we've a `benchstat` tool\n  - https://godoc.org/golang.org/x/perf/cmd/benchstat\n- [ ] [go-critic](https://github.com/go-critic/go-critic) linter enforces several advices from this document\n- [ ] `go mod why -m \u003cmodule\u003e` tells us why a particular module is in the `go.mod` file\n- [ ] `GOGC=off go build ...` should speed up your builds [source](https://twitter.com/mvdan_/status/1107579946501853191)\n- [ ] 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.\n  - by https://twitter.com/bboreham/status/1105036740253937664\n\n### Misc\n- [ ] dump goroutines https://stackoverflow.com/a/27398062/433041\n\n```go\ngo func() {\n\tsigs := make(chan os.Signal, 1)\n\tsignal.Notify(sigs, syscall.SIGQUIT)\n\tbuf := make([]byte, 1\u003c\u003c20)\n\tfor {\n\t\t\u003c-sigs\n\t\tstacklen := runtime.Stack(buf, true)\n\t\tlog.Printf(\"=== received SIGQUIT ===\\n*** goroutine dump...\\n%s\\n*** end\\n\", buf[:stacklen])\n\t}\n}()\n```\n\n- [ ] check interface implementation during compilation\n\n```go\nvar _ io.Reader = (*MyFastReader)(nil)\n```\n\n- [ ] if a param of len is nil then it's zero\n  - https://golang.org/pkg/builtin/#len\n- [ ] anonymous structs are cool\n\n```go\nvar hits struct {\n\tsync.Mutex\n\tn int\n}\nhits.Lock()\nhits.n++\nhits.Unlock()\n```\n\n- [ ] `httputil.DumpRequest` is very useful thing, don't create your own\n  - https://godoc.org/net/http/httputil#DumpRequest\n- [ ] to get call stack we've `runtime.Caller` https://golang.org/pkg/runtime/#Caller\n- [ ] to marshal arbitrary JSON you can marshal to `map[string]interface{}{}`\n- [ ] configure your `CDPATH` so you can do `cd github.com/golang/go` from any directore\n  - add this line to your `bashrc`(or analogue) `export CDPATH=$CDPATH:$GOPATH/src`\n- [ ] simple random element from a slice\n  - `[]string{\"one\", \"two\", \"three\"}[rand.Intn(3)]`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcristaloleg%2Fgo-advice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcristaloleg%2Fgo-advice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcristaloleg%2Fgo-advice/lists"}