Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go packages

Go packages

64a4ba69d50590e592cd8e572454daa8?s=128

Oleg Kovalov

July 16, 2019
Tweet

Transcript

  1. Go packages WARSAW, JULY 16 2019 Oleg Kovalov Allegro https://olegk.dev

  2. - Gopher for ~4 years - Open-source contributor - Engineer

    at allegro.pl core team Website: https://olegk.dev Twitter: @oleg_kovalov Github: @cristaloleg 2
  3. 3

  4. - Short and clear 4

  5. - Short and clear - May be abbreviated when the

    abbreviation is familiar 5
  6. - Short and clear - May be abbreviated when the

    abbreviation is familiar - Abbrv name is easy to understand 6
  7. - 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
  8. - 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
  9. - 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
  10. 10

  11. - If abbreviating is unclear 11

  12. - If abbreviating is unclear - If name is ambiguous

    12
  13. - If abbreviating is unclear - If name is ambiguous

    - snake_case or camelCase 13
  14. - If abbreviating is unclear - If name is ambiguous

    - snake_case or camelCase - psdb (Postgres? Parallel search?) 14
  15. - If abbreviating is unclear - If name is ambiguous

    - snake_case or camelCase - psdb (Postgres? Parallel search?) - srv (service? server? surviving?) 15
  16. - 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
  17. - Avoid giving a package a name that is commonly

    used in client code 17
  18. - 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
  19. - 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
  20. - 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 ?
  21. 21

  22. These std packages are a good names thiefs: - path

    - path/filepath - net/url And you cannot use your variable url easily, oh :( 22
  23. 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
  24. Most of the projects in Go are flat and this

    is fine 24
  25. 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
  26. 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
  27. 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
  28. 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
  29. 29

  30. 30

  31. 31

  32. 32

  33. 33

  34. 34

  35. 35

  36. - Organize by functional responsibility 36

  37. - Organize by functional responsibility - Don’t put all the

    type into models package 37
  38. 38 - Organize by functional responsibility - Don’t put all

    the type into models package - Keeping them in one gives nothing
  39. 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
  40. 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)
  41. 41 . ├── app ├── cmd │ └── <project-name> ├──

    blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  42. 42 . ├── app ├── cmd │ └── <project-name> ├──

    blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  43. 43 . ├── app ├── cmd │ └── <project-name> ├──

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

    ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  45. 45 . ├── app ├── cmd │ └── <project-name> ├──

    blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  46. 46 . ├── app ├── cmd │ └── <project-name> ├──

    blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  47. 47 . ├── app ├── cmd │ └── <project-name> ├──

    blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  48. pkg, internal, misc or util Whatever works for your team

    and you 48 . ├── app ├── cmd │ └── <project-name> ├── blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  49. 49 . ├── app ├── cmd │ └── <project-name> ├──

    blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  50. 50

  51. - util.URLError 51

  52. - util.URLError - common.SanitizeMessage 52

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

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

  55. - util.URLError - common.SanitizeMessage - base.EnsureAbsolutePath - helpers.IsEmail 55 These

    packages say nothing!
  56. 56 - util.URLError - common.SanitizeMessage - base.EnsureAbsolutePath - helpers.IsEmail type

    URLError = app.URLError These packages say nothing!
  57. 57 - util.URLError - common.SanitizeMessage - base.EnsureAbsolutePath - helpers.IsEmail type

    URLError = app.URLError var isEmail = validation.IsEmail These packages say nothing!
  58. - 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
  59. - 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
  60. - 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
  61. - 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
  62. 62 postgres ├── postgres.go ├── user.go ├── stats.go └── user_test.go

    redis ├── redis.go ├── cache.go └── comments.go
  63. 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
  64. 64

  65. - Main packages are not importable 65

  66. - Main packages are not importable - Don’t export from

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

    main, it’s useless - main package is hard to test (see above) 67
  68. 68 package main func init() { config.Load() }

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

    { os.Exit(realMain()) } func realMain() int { log.SetOutput(ioutil.Discard) // ... }
  70. package main func main() { log.Fatal(app.Run()) } 70

  71. 71

  72. - 1 init() per package (pretty please) 72

  73. - 1 init() per package (pretty please) - Do not

    depend on init’s call order 73
  74. - 1 init() per package (pretty please) - Do not

    depend on init’s call order - No heavy tasks 74
  75. - 1 init() per package (pretty please) - Do not

    depend on init’s call order - No heavy tasks - Preferably no I/O, especially blocking 75
  76. - 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
  77. # Python is quite flexible # try: import foo except:

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

    print(‘good try lol’) // Go isn’t so… // package foo func init() { iCanPanicAndYouCanDoNothing() } 78
  79. // but wait, it can be flexible... package foo func

    Init() { iCanPanicButOoooohhYouCanCatchMe() } 79
  80. // 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
  81. package foo func (s *Service) Process() { // let’s start

    a sweet goroutine go func() { iCanPanicAndYouCanDoNothing() }() } 81
  82. 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
  83. package foo func (s *Service) Process() { go func() {

    defer func() { // recovery process }() iCanPanicAndAuthorWillRecover() }() } 83
  84. package foo func (s *Service) Process() { iCanPanicButItDoesntMatter() } 84

  85. package foo func (s *Service) Process() { iCanPanicButItDoesntMatter() } -----------

    package bar func ... { go func() { defer func() { // user defined recovery process }() s.Process() }() } 85
  86. 86

  87. - Exported package variable is mutable by everyone 87

  88. - Exported package variable is mutable by everyone - You

    have no power to control other packages 88
  89. - Exported package variable is mutable by everyone - You

    have no power to control other packages - Tight coupling makes code less flexible 89
  90. - 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
  91. - 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
  92. - 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
  93. - 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
  94. - 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 :(
  95. 95

  96. - Should be simply a container for consts, types, funcs

    96
  97. - Should be simply a container for consts, types, funcs

    - Zero variables is brilliant (both exported and unexported) 97
  98. - Should be simply a container for consts, types, funcs

    - Zero variables is brilliant (both exported and unexported) - Documentation and tests :) 98
  99. 99

  100. - log.Printf A safe action, might be noisy, but harmless

    100
  101. - log.Printf A safe action, might be noisy, but harmless

    - log.Panicf Not idiomatic, but in an edge case might be fine 101
  102. 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)
  103. 103

  104. - doc.go is a README of your package 104

  105. - doc.go is a README of your package - Run

    godoc locally to see how it looks 105
  106. 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
  107. 107

  108. Be conservative in what you send, be liberal in what

    you accept (с) Postel’s Law, Wikipedia 108
  109. Be conservative in what you send, be liberal in what

    you accept (с) Postel’s Law, Wikipedia TCP architecture: 109
  110. 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
  111. 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
  112. func FetchUsers(db Store) ([]*User, error) { ... } 112

  113. func FetchUsers(db Store) ([]*User, error) { ... } - We

    don’t care what is inside: Postgres, Redis, plain file or a mock 113
  114. 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
  115. 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
  116. 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
  117. 117

  118. - There is the “Java-style” interface declaration 118

  119. - There is the “Java-style” interface declaration - Define an

    interface 119
  120. - There is the “Java-style” interface declaration - Define an

    interface - Create a type that implements it 120
  121. 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
  122. - 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
  123. 123

  124. - Go interfaces are in the package that uses it

    124
  125. - Go interfaces are in the package that uses it

    - And this makes Go code easy to extend 125
  126. 126

  127. - Usability ~ Reusability 127

  128. - Usability ~ Reusability - Make application team-friendly 128

  129. - Usability ~ Reusability - Make application team-friendly - Keep

    library easy to use 129
  130. - Usability ~ Reusability - Make application team-friendly - Keep

    library easy to use Animated-emoji-colorful output in app - cool, whatever 130
  131. - 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
  132. 132

  133. - Simple and shy packages 133

  134. - Simple and shy packages - Clear naming is important

    134
  135. - Simple and shy packages - Clear naming is important

    - No global state at all 135
  136. - Simple and shy packages - Clear naming is important

    - No global state at all - Interfaces are for abstraction 136
  137. 137 - Simple and shy packages - Clear naming is

    important - No global state at all - Interfaces are for abstraction - App != Lib
  138. That’s all folks