Slide 1

Slide 1 text

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)

Slide 41

Slide 41 text

41 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 42

Slide 42 text

42 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 43

Slide 43 text

43 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 44

Slide 44 text

44 mock.NewBlog(...) . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 45

Slide 45 text

45 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 46

Slide 46 text

46 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 47

Slide 47 text

47 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

49 . ├── app ├── cmd │ └── ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── .go

Slide 50

Slide 50 text

50

Slide 51

Slide 51 text

- util.URLError 51

Slide 52

Slide 52 text

- util.URLError - common.SanitizeMessage 52

Slide 53

Slide 53 text

- util.URLError - common.SanitizeMessage - base.EnsureAbsolutePath 53

Slide 54

Slide 54 text

- util.URLError - common.SanitizeMessage - base.EnsureAbsolutePath - helpers.IsEmail 54

Slide 55

Slide 55 text

- 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

Slide 62

Slide 62 text

62 postgres ├── postgres.go ├── user.go ├── stats.go └── user_test.go redis ├── redis.go ├── cache.go └── comments.go

Slide 63

Slide 63 text

63 postgres ├── postgres.go ├── user.go ├── stats.go └── user_test.go redis ├── redis.go ├── cache.go └── comments.go cache ├── redis │ └── redis.go store ├── postgres │ ├── postgres.go │ ├── user.go │ └── user_test.go └── redis ├── redis.go └── comments.go

Slide 64

Slide 64 text

64

Slide 65

Slide 65 text

- Main packages are not importable 65

Slide 66

Slide 66 text

- Main packages are not importable - Don’t export from main, it’s useless 66

Slide 67

Slide 67 text

- Main packages are not importable - Don’t export from main, it’s useless - main package is hard to test (see above) 67

Slide 68

Slide 68 text

68 package main func init() { config.Load() }

Slide 69

Slide 69 text

69 package main func init() { config.Load() } func main() { os.Exit(realMain()) } func realMain() int { log.SetOutput(ioutil.Discard) // ... }

Slide 70

Slide 70 text

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

Slide 77

Slide 77 text

# Python is quite flexible # try: import foo except: print(‘good try lol’) 77

Slide 78

Slide 78 text

# Python is quite flexible # try: import foo except: print(‘good try lol’) // Go isn’t so… // package foo func init() { iCanPanicAndYouCanDoNothing() } 78

Slide 79

Slide 79 text

// 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

Slide 112

Slide 112 text

func FetchUsers(db Store) ([]*User, error) { ... } 112

Slide 113

Slide 113 text

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

Slide 138

Slide 138 text

That’s all folks