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. Go packages
    WARSAW, JULY 16 2019
    Oleg Kovalov
    Allegro
    https://olegk.dev

    View Slide

  2. - Gopher for ~4 years
    - Open-source contributor
    - Engineer at allegro.pl core team
    Website: https://olegk.dev
    Twitter: @oleg_kovalov
    Github: @cristaloleg
    2

    View Slide

  3. 3

    View Slide

  4. - Short and clear
    4

    View Slide

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

    View Slide

  6. - Short and clear
    - May be abbreviated when the abbreviation is familiar
    - Abbrv name is easy to understand
    6

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  10. 10

    View Slide

  11. - If abbreviating is unclear
    11

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. - If abbreviating is unclear
    - If name is ambiguous
    - snake_case or camelCase
    - psdb (Postgres? Parallel search?)
    - srv (service? server? surviving?)
    15

    View Slide

  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

    View Slide

  17. - Avoid giving a package a name that is commonly used in client code
    17

    View Slide

  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

    View Slide

  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

    View Slide

  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 ?

    View Slide

  21. 21

    View Slide

  22. These std packages are a good names thiefs:
    - path
    - path/filepath
    - net/url
    And you cannot use your variable url easily, oh :(
    22

    View Slide

  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

    View Slide

  24. Most of the projects in Go are flat and this is fine
    24

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  29. 29

    View Slide

  30. 30

    View Slide

  31. 31

    View Slide

  32. 32

    View Slide

  33. 33

    View Slide

  34. 34

    View Slide

  35. 35

    View Slide

  36. - Organize by functional responsibility
    36

    View Slide

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

    View Slide

  38. 38
    - Organize by functional responsibility
    - Don’t put all the type into models package
    - Keeping them in one gives nothing

    View Slide

  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

    View Slide

  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)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  48. 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

    View Slide

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

    View Slide

  50. 50

    View Slide

  51. - util.URLError
    51

    View Slide

  52. - util.URLError
    - common.SanitizeMessage
    52

    View Slide

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

    View Slide

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

    View Slide

  55. - util.URLError
    - common.SanitizeMessage
    - base.EnsureAbsolutePath
    - helpers.IsEmail
    55
    These packages say nothing!

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  64. 64

    View Slide

  65. - Main packages are not importable
    65

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  70. package main
    func main() {
    log.Fatal(app.Run())
    }
    70

    View Slide

  71. 71

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  79. // but wait, it can be flexible...
    package foo
    func Init() {
    iCanPanicButOoooohhYouCanCatchMe()
    }
    79

    View Slide

  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

    View Slide

  81. package foo
    func (s *Service) Process() {
    // let’s start a sweet goroutine
    go func() {
    iCanPanicAndYouCanDoNothing()
    }()
    }
    81

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  85. package foo
    func (s *Service) Process() {
    iCanPanicButItDoesntMatter()
    }
    -----------
    package bar
    func ... {
    go func() {
    defer func() {
    // user defined recovery process
    }()
    s.Process()
    }()
    }
    85

    View Slide

  86. 86

    View Slide

  87. - Exported package variable is mutable by everyone
    87

    View Slide

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

    View Slide

  89. - Exported package variable is mutable by everyone
    - You have no power to control other packages
    - Tight coupling makes code less flexible
    89

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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 :(

    View Slide

  95. 95

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  99. 99

    View Slide

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

    View Slide

  101. - log.Printf
    A safe action, might be noisy, but harmless
    - log.Panicf
    Not idiomatic, but in an edge case might be fine
    101

    View Slide

  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)

    View Slide

  103. 103

    View Slide

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

    View Slide

  105. - doc.go is a README of your package
    - Run godoc locally to see how it looks
    105

    View Slide

  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

    View Slide

  107. 107

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  113. func FetchUsers(db Store) ([]*User, error) { ... }
    - We don’t care what is inside: Postgres, Redis, plain file or a mock
    113

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  117. 117

    View Slide

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

    View Slide

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

    View Slide

  120. - There is the “Java-style” interface declaration
    - Define an interface
    - Create a type that implements it
    120

    View Slide

  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

    View Slide

  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

    View Slide

  123. 123

    View Slide

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

    View Slide

  125. - Go interfaces are in the package that uses it
    - And this makes Go code easy to extend
    125

    View Slide

  126. 126

    View Slide

  127. - Usability ~ Reusability
    127

    View Slide

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

    View Slide

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

    View Slide

  130. - Usability ~ Reusability
    - Make application team-friendly
    - Keep library easy to use
    Animated-emoji-colorful output in app - cool, whatever
    130

    View Slide

  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

    View Slide

  132. 132

    View Slide

  133. - Simple and shy packages
    133

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  137. 137
    - Simple and shy packages
    - Clear naming is important
    - No global state at all
    - Interfaces are for abstraction
    - App != Lib

    View Slide

  138. That’s all folks

    View Slide