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

Common Pitfalls in Go

Common Pitfalls in Go

Slides from a talk at LeedsGo

Avatar for Amelia Ikeda

Amelia Ikeda

February 28, 2019
Tweet

Other Decks in Programming

Transcript

  1. COMMON PITFALLS IN GO LEEDS GO, FEB 28TH 2019 Amelia

    Ikeda Sky Betting & Gaming twitter.com/ameliaikeda pronouns: she/they
  2. Today’s (glorious) blather. Things we all do 01 Interface Usage

    Not Freeing Resources Goroutine Exit Cases Questions? 02 03 04
  3. Not using the right tool for the job import (

    “testing” “.../testify/assert” ) func TestSomething(t *testing.T) { foo, err := doSomething() assert.NoError(t, err) assert.Equal(t, “test”, foo.M) }
  4. Not using the right tool for the job import (

    “testing” “.../testify/assert” ) func TestSomething(t *testing.T) { foo, err := doSomething() assert.NoError(t, err) assert.Equal(t, “test”, foo.M) } Or: how I learned to stop worrying and love if err != nil panics, rather explosively!
  5. TYPES & METHODS 02. A SIMPLE TYPE SYSTEM type Abser

    interface { Abs() float64 } Interfaces specify behaviors. An interface type defines a set of methods: Types implement interfaces implicitly. There is no “implements” declaration. func PrintAbs(a Abser) { fmt.Printf("Absolute value: %.2f\n", a.Abs()) } PrintAbs(MyFloat(-10)) PrintAbs(Point{3, 4}) A type that implements those methods implements the interface:
  6. { TENETS OF GO DESIGN Simplicity. Types implement interfaces implicitly.

    There is no “implements” declaration. 01. BACKGROUND
  7. Maybe you start with a struct... type Foo struct {

    name string } func (f *Foo) Name() string { return f.name } func (f *Foo) SetName(name string) { f.name = name } func New(name string) *Foo { return &Foo{name: name} }
  8. And then set your return type to the new interface

    type Namer interface { Name() string SetName(string) } func New(name string) Namer { return &foo{name: name} }
  9. Always return concrete types! type Namer interface { Name() string

    SetName(string) } func New(name string) *Foo { return &Foo{name: name} }
  10. Be conservative in what you send type Namer interface {

    Name() string SetName(string) } func Wrap(n Namer) *Foo { return &Foo{namer: n} } Send a concrete type to the caller, not an abstraction.
  11. Be liberal in what you accept from others type Namer

    interface { Name() string SetName(string) } func Wrap(n Namer) *Foo { return &Foo{namer: n} } Accept interfaces, instead of concrete types. Interfaces are implicit!
  12. Want guarantees from interfaces? // enforce a compiler check. //

    bonus: alt+enter in GoLand // code generation <3 var _ Namer = (*Foo)(nil) Enforce compiler checks.
  13. Define interfaces where you use them. type userFacing interface {

    UserError() string } // in the same package: if e, ok := err.(userFacing); ok { return errors.WithMessage( err, e.UserError() ) } Use interfaces wisely.
  14. Not Freeing Resources. The main culprits: • http.Response.Body • time.Ticker

    • _, cancel := context.WithCancel() • _, cancel := context.WithDeadline()
  15. Not Freeing Resources import ( “io/ioutil” “net/http” ) func get()

    ([]byte, error) { res, err := http.Get(“...”) if err != nil { return “”, err } return ioutil.ReadAll(res.Body) } HTTP Response Bodies Leaks a file descriptor.
  16. Not Freeing Resources if !ok && body != nil {

    rc = ioutil.NopCloser(body) } res.Body is always explicitly non-nil, so deferring a Close() is safe. Http Response bodies should always be closed res, err := http.Get(“...”) if err != nil { return “”, err } defer res.Body.Close() Always call Body.Close()! In a non-failure scenario, this leaks a file descriptor.
  17. Not Freeing Resources: os.File f, err := os.Open(“file.txt”) // handle

    err defer f.Close() Opening a file opens a “file descriptor” in the OS. These are limited! var buf bytes.Buffer buf.ReadFrom(f) Just defer closing after you’re done with a file. Also: use buffers!
  18. Not Freeing Resources package context func WithCancel( ctx context.Context )

    (context.Context, func()) func WithDeadline( ctx context.Context, d time.Duration ) (context.Context, func())
  19. Not Freeing Resources package context func WithCancel( ctx context.Context )

    (context.Context, func()) func WithDeadline( ctx context.Context, d time.Duration ) (context.Context, func())
  20. Tickers: well-documented costs. d := time.Second*5 ticker := time.NewTicker(d) for

    { <-ticker.C // blocks work() } What does this leak?
  21. Tickers: well-documented costs. d := time.Second*5 ticker := time.NewTicker(d) for

    { <-ticker.C // blocks if work() { ticker.Stop() return } } Call ticker.Stop()
  22. Handling Goroutine Exits t := time.NewTicker(1*time.Second) go func() { for

    { <-t.C fmt.Println("tick") } fmt.Println("stopped!") }() // wait, even t.Stop()
  23. Handling Goroutine Exits t := time.NewTicker(1*time.Second) go func() { for

    { <-t.C fmt.Println("tick") } fmt.Println("stopped!") }() // wait, even t.Stop() This goroutine never exits.
  24. Handling Goroutine Exits t := time.NewTicker(1*time.Second) go func() { for

    { <-t.C fmt.Println("tick") } fmt.Println("stopped!") }() // wait, even t.Stop() go vet even complains about this line being unreachable
  25. Handling Goroutine Exits t := time.NewTicker(1*time.Second) done := make(chan bool)

    go func() { for { select { case <-t.C: fmt.Println("tick") case <-done: t.Stop() return } } }() Use for-select with an explicit exit case.
  26. Handling Goroutine Exits t := time.NewTicker(1*time.Second) done := make(chan bool)

    go func() { for { select { case <-t.C: fmt.Println("tick") case <-done: t.Stop() return } } }() Use for-select with an explicit exit case.