I don’t have a Soundcloud...but 8 - Gopher for ~4 years - Open-source contributor - Go-critic - static analysis tool - Go-advice - list of Go tips @ GoGoConf 2018
I don’t have a Soundcloud...but 9 - Gopher for ~4 years - Open-source contributor - Go-critic - static analysis tool - Go-advice - list of Go tips @ GoGoConf 2018 - Engineer at allegro.pl core team
- Gopher for ~4 years - Open-source contributor - Go-critic - static analysis tool - Go-advice - list of Go tips @ GoGoConf 2018 - Engineer at allegro.pl core team Website: https://olegk.dev Twitter: @oleg_kovalov Github: @cristaloleg I don’t have a Soundcloud...but 10 Twitter & slides
Naming - Short and clear - May be abbreviated when the abbreviation is familiar - Abbrv name is easy to understand See standard packages for good examples - io (input and output) 16
- Short and clear - May be abbreviated when the abbreviation is familiar - Abbrv name is easy to understand See standard packages for good examples - io (input and output) - strconv (string conversion) Naming 17
- Short and clear - May be abbreviated when the abbreviation is familiar - Abbrv name is easy to understand See standard packages for good examples - io (input and output) - strconv (string conversion) - syscall (system call) Naming 18
- If abbreviating is unclear - If name is ambiguous - snake_case or camelCase - psdb (Postgres? Parallel search?) - srv (service? server? surviving?) Naming don’ts 24
- Avoid giving a package a name that is commonly used in client code - buf is a good variable name for a buffer - the buffered I/O package is called bufio (buffer + io) Don’t steal good names 27
- Avoid giving a package a name that is commonly used in client code - buf is a good variable name for a buffer - the buffered I/O package is called bufio (buffer + io) - client is a good name for...client - but not a package name Don’t steal good names 28
- Avoid giving a package a name that is commonly used in client code - buf is a good variable name for a buffer - the buffered I/O package is called bufio (buffer + io) - client is a good name for...client - but not a package name - imagine a replacement for it Don’t steal good names 29 c ? clnt ? cli ? clientHTTP ?
These std packages are a good names thiefs: - path - path/filepath - net/url And you cannot use your variable url easily, oh :( ...and few from std packages 31
...and few from std packages 32 These std packages are a good names thiefs: - path - path/filepath - net/url And you cannot use your variable url easily, oh :( But there are good examples: - strings - bytes - net/urls instead of net/url looks good enough
Most of the projects in Go are flat and this is fine - Try to keep it clean github.com/foo/bar/pl/goava/src/main/code/go/validation Try to keep clean import paths 34
Most of the projects in Go are flat and this is fine - Try to keep it clean github.com/foo/bar/pl/goava/src/main/code/go/validation - Avoid do src/ or pkg/ (in most cases) gitlab.com/foo/bar/pkg/process Try to keep clean import paths 35
Write shy code - modules that don’t reveal anything unnecessary to other modules and that don’t rely on other modules' implementations. --- Dave Thomas (with Andy Hunt, he co-authored The Pragmatic Programmer) Shy packages 36
Write shy code - modules that don’t reveal anything unnecessary to other modules and that don’t rely on other modules' implementations. --- Dave Thomas (with Andy Hunt, he co-authored The Pragmatic Programmer) And shy also means: 1 package = 1 thing Shy packages 37
Models package is a weak idea, so 47 - Organize by functional responsibility - Don’t put all the type into models package - Keeping them in one gives nothing
Models package is a weak idea, so 48 - Organize by functional responsibility - Don’t put all the type into models package - Keeping them in one gives nothing - In the end this will be a spaghetti code
Models package is a weak idea, so 49 - Organize by functional responsibility - Don’t put all the type into models package - Keeping them in one gives nothing - In the end this will be a spaghetti code - (The same applies to the interfaces, implementations and so on)
pkg, internal, misc or util Whatever works for your team and you One of the possible structures 57 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go
- Use multiple files - Do not separate structs and their methods (not a C/C++) - It’s okay to store few structs in one file (it’s not Java after all) - And keep test files next to code store ├── postgres │ ├── postgres.go │ ├── user.go │ └── user_test.go └── redis ├── redis.go └── comments.go Inside the package 67
- Use multiple files - Do not separate structs and their methods (not a C/C++) - It’s okay to store few structs in one file (it’s not Java after all) - And keep test files next to code store ├── postgres │ ├── postgres.go │ ├── user.go │ └── user_test.go └── redis ├── redis.go └── comments.go // type Comment and Comment.HasImage method Inside the package 68
- Use multiple files - Do not separate structs and their methods (not a C/C++) - It’s okay to store few structs in one file (it’s not Java after all) - And keep test files next to code store ├── postgres │ ├── postgres.go │ ├── user.go // type User and type Credentials │ └── user_test.go └── redis ├── redis.go └── comments.go Inside the package 69
- Use multiple files - Do not separate structs and their methods (not a C/C++) - It’s okay to store few structs in one file (it’s not Java after all) - And keep test files next to code store ├── postgres │ ├── postgres.go │ ├── user.go │ └── user_test.go └── redis ├── redis.go └── comments.go Inside the package 70
- 1 init() per package (pretty please) - Do not depend on init’s call order - No heavy tasks - Preferably no I/O, especially blocking Keep your init() light 84
- 1 init() per package (pretty please) - Do not depend on init’s call order - No heavy tasks - Preferably no I/O, especially blocking - Better to not have it at all Keep your init() light 85
panic in init() // but wait, it can be flexible... package foo func Init() { iCanPanicButOoooohhYouCanCatchMe() } ----------- package bar func ... { defer func() { if r := recover(); r != nil { // process a panic } }() foo.Init() } 89
package foo func (s *Service) Process() { // let’s start a sweet goroutine go func() { iCanPanicAndYouCanDoNothing() }() } I heard you like goroutines 90
package foo func (s *Service) Process() { // let’s start a sweet goroutine go func() { iCanPanicAndYouCanDoNothing() }() } ----------- package bar func ... { // but internal goroutine // isn’t accessible here :( s.Process() } I heard you like goroutines 91
package foo func (s *Service) Process() { go func() { defer func() { // recovery process }() iCanPanicAndAuthorWillRecover() }() } I heard you like goroutines 92
package foo func (s *Service) Process() { iCanPanicButItDoesntMatter() } ----------- package bar func ... { go func() { defer func() { // user defined recovery process }() s.Process() }() } I heard you like goroutines 94
- Exported package variable is mutable by everyone - You have no power to control other packages - Tight coupling makes code less flexible Global state 98
- Exported package variable is mutable by everyone - You have no power to control other packages - Tight coupling makes code less flexible - You cannot mock a package! Global state 99
- Exported package variable is mutable by everyone - You have no power to control other packages - Tight coupling makes code less flexible - You cannot mock a package! - Logger, metrics? - mmmmaybe Global state 100
- Exported package variable is mutable by everyone - You have no power to control other packages - Tight coupling makes code less flexible - You cannot mock a package! - Logger, metrics? - mmmmaybe - Config, flags? - better no Global state 101
- Exported package variable is mutable by everyone - You have no power to control other packages - Tight coupling makes code less flexible - You cannot mock a package! - Logger, metrics? - mmmmaybe - Config, flags? - better no - DB driver? - NONONO Global state 102
- Exported package variable is mutable by everyone - You have no power to control other packages - Tight coupling makes code less flexible - You cannot mock a package! - Logger, metrics? - mmmmaybe - Config, flags? - better no - DB driver? - NONONO Global state 103 by the way unexported variables are also bad :(
- Should be simply a container for consts, types, funcs - Zero variables is brilliant (both exported and unexported) Good package == stateless container 106
- Should be simply a container for consts, types, funcs - Zero variables is brilliant (both exported and unexported) - Documentation and tests :) Good package == stateless container 107
Logging is cool, but... 111 - log.Printf A safe action, might be noisy, but harmless - log.Panicf Not idiomatic, but in an edge case might be fine - log.Fatalf Evil has no boundaries, omit it, no one can survive os.Exit(1) (even defer)
Forgotten doc.go 115 - doc.go is a README of your package - Run godoc locally to see how it looks - Also add package documentation // Package bytes implements functions for the manipulation of byte slices. // It is analogous to the facilities of the strings package. package bytes
Accept interfaces, return structs Be conservative in what you send, be liberal in what you accept (с) Postel’s Law, Wikipedia TCP architecture: - We don’t care about how packets go through network 120
Be conservative in what you send, be liberal in what you accept (с) Postel’s Law, Wikipedia TCP architecture: - We don’t care about how packets go through network - but we expect them in a well defined order to process Accept interfaces, return structs 121
func FetchUsers(db Store) ([]*User, error) { ... } - We don’t care what is inside: Postgres, Redis, plain file or a mock Accept interfaces, return structs 123
func FetchUsers(db Store) ([]*User, error) { ... } - We don’t care what is inside: Postgres, Redis, plain file or a mock - Until it has desired API for us Accept interfaces, return structs 124
func FetchUsers(db Store) ([]*User, error) { ... } - We don’t care what is inside: Postgres, Redis, plain file or a mock - Until it has desired API for us - But we know for sure that we need a specific struct User Accept interfaces, return structs 125
func FetchUsers(db Store) ([]*User, error) { ... } - We don’t care what is inside: Postgres, Redis, plain file or a mock - Until it has desired API for us - But we know for sure that we need a specific struct User Tip: return error as interface, not as a struct Accept interfaces, return structs 126
Where to declare interfaces? 131 - There is the “Java-style” interface declaration - Define an interface - Create a type that implements it - Often you have one interface and one implementation
Where to declare interfaces? - There is the “Java-style” interface declaration - Define an interface - Create a type that implements it - Often you have one interface and one implementation That’s not a “Go style” ʕ◔ϖ◔ʔ 132
- A module is a collection of Go packages that are versioned as a single unit - A module is a tree/directory of Go source files with a go.mod file in the root ├── cmd │ └── main.go ├── consum │ ├── consum.go │ └── handlers.go ├── produce │ ├── handlers.go │ ├── handlers_test.go │ └── processing.go ├── go.mod └── go.sum What is a module? 148
- A module is a collection of Go packages that are versioned as a single unit - A module is a tree/directory of Go source files with a go.mod file in the root ├── cmd │ └── main.go ├── consum │ ├── consum.go │ └── handlers.go ├── produce │ ├── handlers.go │ ├── handlers_test.go │ └── processing.go ├── go.mod └── go.sum What is a module? 149 Can leave outside of GOPATH
Import compatibility rule 151 - If an old and a new package have the same import path - the new package must be backwards-compatible with the old package
Import compatibility rule 152 - If an old and a new package have the same import path - the new package must be backwards-compatible with the old package import ( "github.com/golang/hello/hey" // v0 or v1 "github.com/golang/hello/v2/hey" )
go mod commands 154 Usage: go mod [arguments] The commands are: download download modules to local cache edit edit go.mod from tools or scripts graph print module requirement graph init initialize new module in current directory tidy add missing and remove unused modules vendor make vendored copy of dependencies verify verify dependencies have expected content why explain why packages or modules are needed
Let’s start with modules $ mkdir /mygo/hello $ cd /mygo/hello $ go mod init github.com/golang/hello go: creating new go.mod: module github.com/golang/hello $ ls go.mod 156
Let’s start with modules 157 $ mkdir /mygo/hello $ cd /mygo/hello $ go mod init github.com/golang/hello go: creating new go.mod: module github.com/golang/hello $ ls go.mod $ cat go.mod module github.com/golang/hello
$ git clone https://github.com/go-gitea/gitea Cloning into gitea... $ cd gitea $ go mod init go: creating new go.mod: module github.com/go-gitea/gitea go: copying requirements from Gopkg.lock Convert to modules 159
How to read go.sum? 168 cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/azure-sdk-for-go v16.0.0+incompatible h1:gr1qKY/Ll72VjFTZmaBwRK1yQHAxCnV25ekOKroc9ws= github.com/Azure/azure-sdk-for-go v16.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= // you might want to use go mod verify
# same (@latest is default for 'go get') $ go get github.com/gorilla/mux@latest # records v1.6.2 $ go get github.com/gorilla/[email protected] $ go get github.com/gorilla/mux@e3702bed2 go get understands versioning 171
go get understands versioning # same (@latest is default for 'go get') $ go get github.com/gorilla/mux@latest # records v1.6.2 $ go get github.com/gorilla/[email protected] $ go get github.com/gorilla/mux@e3702bed2 # records v0.0.0-20180517173623-c85619274f5d $ go get github.com/gorilla/mux@c856192 172
go get understands versioning 173 # same (@latest is default for 'go get') $ go get github.com/gorilla/mux@latest # records v1.6.2 $ go get github.com/gorilla/[email protected] $ go get github.com/gorilla/mux@e3702bed2 # records v0.0.0-20180517173623-c85619274f5d $ go get github.com/gorilla/mux@c856192 # records current meaning of master $ go get github.com/gorilla/mux@master
# go and get a repo $ go get github.com/go-gitea/gitea $ cd ~/go/src/github.com/go-gitea/gitea # set a env var and check why do we use this dependency $ export GO111MODULE=on $ go mod why github.com/RoaringBitmap/roaring go mod why 175
go mod why 176 # go and get a repo $ go get github.com/go-gitea/gitea $ cd ~/go/src/github.com/go-gitea/gitea # set a env var and check why do we use this dependency $ export GO111MODULE=on $ go mod why github.com/RoaringBitmap/roaring # github.com/RoaringBitmap/roaring code.gitea.io/gitea/modules/indexer github.com/blevesearch/bleve github.com/blevesearch/bleve/index/scorch github.com/RoaringBitmap/roaring
# install $ go get github.com/rogpeppe/gohack $ gohack get example.com/foo/bar # will clone repo into $HOME/gohack/example.com/foo/bar # will add replace statement $ cat go.mod replace example.com/foo/bar => /home/rog/gohack/example.com/foo/bar gohack by Roger Peppe 178
gohack by Roger Peppe 179 # install $ go get github.com/rogpeppe/gohack $ gohack get example.com/foo/bar # will clone repo into $HOME/gohack/example.com/foo/bar # will add replace statement $ cat go.mod replace example.com/foo/bar => /home/rog/gohack/example.com/foo/bar # to remove specific replace: $ gohack undo example.com/foo/bar # to remove all replaces: $ gohack undo
- Usability ~ Reusability - Make application team-friendly - Keep library easy to use Animated-emoji-colorful output in app - cool, whatever Math library with zero allocation HTTP router - thanks, but no Application != Library 189
Summary 196 - Simple and shy packages - Clear naming is important - No global state at all - Interfaces are for abstraction - Modules == Go ecosystem 2.0
Summary 197 - Simple and shy packages - Clear naming is important - No global state at all - Interfaces are for abstraction - Modules == Go ecosystem 2.0 - App != Lib