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

Go packages

Go packages

Oleg Kovalov

July 16, 2019
Tweet

More Decks by Oleg Kovalov

Other Decks in Programming

Transcript

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

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

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

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

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

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

    - snake_case or camelCase - psdb (Postgres? Parallel search?) - srv (service? server? surviving?) 15
  10. - 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
  11. - 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
  12. - 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
  13. - 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 ?
  14. 21

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

    - path/filepath - net/url And you cannot use your variable url easily, oh :( 22
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 29

  22. 30

  23. 31

  24. 32

  25. 33

  26. 34

  27. 35

  28. 38 - Organize by functional responsibility - Don’t put all

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

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

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

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

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

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

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

    blog ├── comment ├── mock ├── pkg │ ├── superpay │ └── validation ├── store │ └── postgres ├── user └── <project-name>.go
  38. 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
  39. 49 . ├── app ├── cmd │ └── <project-name> ├──

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

  41. 57 - util.URLError - common.SanitizeMessage - base.EnsureAbsolutePath - helpers.IsEmail type

    URLError = app.URLError var isEmail = validation.IsEmail These packages say nothing!
  42. - 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
  43. - 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
  44. - 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
  45. - 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
  46. 62 postgres ├── postgres.go ├── user.go ├── stats.go └── user_test.go

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

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

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

    { os.Exit(realMain()) } func realMain() int { log.SetOutput(ioutil.Discard) // ... }
  51. 71

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

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

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

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

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

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

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

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

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

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

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

    have no power to control other packages - Tight coupling makes code less flexible 89
  66. - 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
  67. - 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
  68. - 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
  69. - 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
  70. - 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 :(
  71. 95

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

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

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

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

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

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

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

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

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

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

    don’t care what is inside: Postgres, Redis, plain file or a mock 113
  86. 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
  87. 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
  88. 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
  89. 117

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

    interface - Create a type that implements it 120
  91. 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
  92. - 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
  93. 123

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

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

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

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

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

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

    important - No global state at all - Interfaces are for abstraction - App != Lib