Go packages
WARSAW, JULY 16 2019
Oleg Kovalov
Allegro
https://olegk.dev
Slide 2
Slide 2 text
- Gopher for ~4 years
- Open-source contributor
- Engineer at allegro.pl core team
Website: https://olegk.dev
Twitter: @oleg_kovalov
Github: @cristaloleg
2
Slide 3
Slide 3 text
3
Slide 4
Slide 4 text
- Short and clear
4
Slide 5
Slide 5 text
- Short and clear
- May be abbreviated when the abbreviation is familiar
5
Slide 6
Slide 6 text
- Short and clear
- May be abbreviated when the abbreviation is familiar
- Abbrv name is easy to understand
6
Slide 7
Slide 7 text
- 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)
7
Slide 8
Slide 8 text
- 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)
8
Slide 9
Slide 9 text
- 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)
9
Slide 10
Slide 10 text
10
Slide 11
Slide 11 text
- If abbreviating is unclear
11
Slide 12
Slide 12 text
- If abbreviating is unclear
- If name is ambiguous
12
Slide 13
Slide 13 text
- If abbreviating is unclear
- If name is ambiguous
- snake_case or camelCase
13
Slide 14
Slide 14 text
- If abbreviating is unclear
- If name is ambiguous
- snake_case or camelCase
- psdb (Postgres? Parallel search?)
14
Slide 15
Slide 15 text
- If abbreviating is unclear
- If name is ambiguous
- snake_case or camelCase
- psdb (Postgres? Parallel search?)
- srv (service? server? surviving?)
15
Slide 16
Slide 16 text
- If abbreviating is unclear
- If name is ambiguous
- snake_case or camelCase
- psdb (Postgres? Parallel search?)
- srv (service? server? surviving?)
- buf (buffer? buffer what? WoW buff?)
16
Slide 17
Slide 17 text
- Avoid giving a package a name that is commonly used in client code
17
Slide 18
Slide 18 text
- 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)
18
Slide 19
Slide 19 text
- 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
19
Slide 20
Slide 20 text
- 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
20
c ? clnt ? cli ?
clientHTTP ?
Slide 21
Slide 21 text
21
Slide 22
Slide 22 text
These std packages are a good names thiefs:
- path
- path/filepath
- net/url
And you cannot use your variable url easily, oh :(
22
Slide 23
Slide 23 text
23
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
Slide 24
Slide 24 text
Most of the projects in Go are flat and this is fine
24
Slide 25
Slide 25 text
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
25
Slide 26
Slide 26 text
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
26
Slide 27
Slide 27 text
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)
27
Slide 28
Slide 28 text
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
28
Slide 29
Slide 29 text
29
Slide 30
Slide 30 text
30
Slide 31
Slide 31 text
31
Slide 32
Slide 32 text
32
Slide 33
Slide 33 text
33
Slide 34
Slide 34 text
34
Slide 35
Slide 35 text
35
Slide 36
Slide 36 text
- Organize by functional responsibility
36
Slide 37
Slide 37 text
- Organize by functional responsibility
- Don’t put all the type into models package
37
Slide 38
Slide 38 text
38
- Organize by functional responsibility
- Don’t put all the type into models package
- Keeping them in one gives nothing
Slide 39
Slide 39 text
39
- 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
Slide 40
Slide 40 text
40
- 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
48
.
├── app
├── cmd
│ └──
├── blog
├── comment
├── mock
├── pkg
│ ├── superpay
│ └── validation
├── store
│ └── postgres
├── user
└── .go
- util.URLError
- common.SanitizeMessage
- base.EnsureAbsolutePath
- helpers.IsEmail
55
These packages say nothing!
Slide 56
Slide 56 text
56
- util.URLError
- common.SanitizeMessage
- base.EnsureAbsolutePath
- helpers.IsEmail
type URLError = app.URLError
These packages say nothing!
Slide 57
Slide 57 text
57
- util.URLError
- common.SanitizeMessage
- base.EnsureAbsolutePath
- helpers.IsEmail
type URLError = app.URLError
var isEmail = validation.IsEmail
These packages say nothing!
Slide 58
Slide 58 text
- 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
58
Slide 59
Slide 59 text
- 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
59
Slide 60
Slide 60 text
- 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
60
Slide 61
Slide 61 text
- 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
61
package main
func main() {
log.Fatal(app.Run())
}
70
Slide 71
Slide 71 text
71
Slide 72
Slide 72 text
- 1 init() per package (pretty please)
72
Slide 73
Slide 73 text
- 1 init() per package (pretty please)
- Do not depend on init’s call order
73
Slide 74
Slide 74 text
- 1 init() per package (pretty please)
- Do not depend on init’s call order
- No heavy tasks
74
Slide 75
Slide 75 text
- 1 init() per package (pretty please)
- Do not depend on init’s call order
- No heavy tasks
- Preferably no I/O, especially blocking
75
Slide 76
Slide 76 text
- 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
76
// but wait, it can be flexible...
package foo
func Init() {
iCanPanicButOoooohhYouCanCatchMe()
}
79
Slide 80
Slide 80 text
// 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()
} 80
Slide 81
Slide 81 text
package foo
func (s *Service) Process() {
// let’s start a sweet goroutine
go func() {
iCanPanicAndYouCanDoNothing()
}()
}
81
Slide 82
Slide 82 text
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()
}
82
Slide 83
Slide 83 text
package foo
func (s *Service) Process() {
go func() {
defer func() {
// recovery process
}()
iCanPanicAndAuthorWillRecover()
}()
}
83
Slide 84
Slide 84 text
package foo
func (s *Service) Process() {
iCanPanicButItDoesntMatter()
}
84
Slide 85
Slide 85 text
package foo
func (s *Service) Process() {
iCanPanicButItDoesntMatter()
}
-----------
package bar
func ... {
go func() {
defer func() {
// user defined recovery process
}()
s.Process()
}()
}
85
Slide 86
Slide 86 text
86
Slide 87
Slide 87 text
- Exported package variable is mutable by everyone
87
Slide 88
Slide 88 text
- Exported package variable is mutable by everyone
- You have no power to control other packages
88
Slide 89
Slide 89 text
- Exported package variable is mutable by everyone
- You have no power to control other packages
- Tight coupling makes code less flexible
89
Slide 90
Slide 90 text
- 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!
90
Slide 91
Slide 91 text
- 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
91
Slide 92
Slide 92 text
- 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
92
Slide 93
Slide 93 text
- 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
93
Slide 94
Slide 94 text
- 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
94
by the way unexported variables
are also bad :(
Slide 95
Slide 95 text
95
Slide 96
Slide 96 text
- Should be simply a container for consts, types, funcs
96
Slide 97
Slide 97 text
- Should be simply a container for consts, types, funcs
- Zero variables is brilliant (both exported and unexported)
97
Slide 98
Slide 98 text
- Should be simply a container for consts, types, funcs
- Zero variables is brilliant (both exported and unexported)
- Documentation and tests :)
98
Slide 99
Slide 99 text
99
Slide 100
Slide 100 text
- log.Printf
A safe action, might be noisy, but harmless
100
Slide 101
Slide 101 text
- log.Printf
A safe action, might be noisy, but harmless
- log.Panicf
Not idiomatic, but in an edge case might be fine
101
Slide 102
Slide 102 text
102
- 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)
Slide 103
Slide 103 text
103
Slide 104
Slide 104 text
- doc.go is a README of your package
104
Slide 105
Slide 105 text
- doc.go is a README of your package
- Run godoc locally to see how it looks
105
Slide 106
Slide 106 text
106
- 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
Slide 107
Slide 107 text
107
Slide 108
Slide 108 text
Be conservative in what you send, be liberal in what you accept
(с) Postel’s Law, Wikipedia
108
Slide 109
Slide 109 text
Be conservative in what you send, be liberal in what you accept
(с) Postel’s Law, Wikipedia
TCP architecture:
109
Slide 110
Slide 110 text
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
110
Slide 111
Slide 111 text
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
111
func FetchUsers(db Store) ([]*User, error) { ... }
- We don’t care what is inside: Postgres, Redis, plain file or a mock
113
Slide 114
Slide 114 text
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
114
Slide 115
Slide 115 text
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
115
Slide 116
Slide 116 text
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
116
Slide 117
Slide 117 text
117
Slide 118
Slide 118 text
- There is the “Java-style” interface declaration
118
Slide 119
Slide 119 text
- There is the “Java-style” interface declaration
- Define an interface
119
Slide 120
Slide 120 text
- There is the “Java-style” interface declaration
- Define an interface
- Create a type that implements it
120
Slide 121
Slide 121 text
121
- There is the “Java-style” interface declaration
- Define an interface
- Create a type that implements it
- Often you have one interface and one implementation
Slide 122
Slide 122 text
- 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”
ʕ◔ϖ◔ʔ
122
Slide 123
Slide 123 text
123
Slide 124
Slide 124 text
- Go interfaces are in the package that uses it
124
Slide 125
Slide 125 text
- Go interfaces are in the package that uses it
- And this makes Go code easy to extend
125
Slide 126
Slide 126 text
126
Slide 127
Slide 127 text
- Usability ~ Reusability
127
Slide 128
Slide 128 text
- Usability ~ Reusability
- Make application team-friendly
128
Slide 129
Slide 129 text
- Usability ~ Reusability
- Make application team-friendly
- Keep library easy to use
129
Slide 130
Slide 130 text
- Usability ~ Reusability
- Make application team-friendly
- Keep library easy to use
Animated-emoji-colorful output in app - cool, whatever
130
Slide 131
Slide 131 text
- 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
131
Slide 132
Slide 132 text
132
Slide 133
Slide 133 text
- Simple and shy packages
133
Slide 134
Slide 134 text
- Simple and shy packages
- Clear naming is important
134
Slide 135
Slide 135 text
- Simple and shy packages
- Clear naming is important
- No global state at all
135
Slide 136
Slide 136 text
- Simple and shy packages
- Clear naming is important
- No global state at all
- Interfaces are for abstraction
136
Slide 137
Slide 137 text
137
- Simple and shy packages
- Clear naming is important
- No global state at all
- Interfaces are for abstraction
- App != Lib