Coding Tips That Actually Matter

Coding Tips That Actually Matter

Error Handling

Errors are values that implement the Error() string interface.

Don’t just check errors. Handle them gracefully.

Stack traces are unfriendly to errors. They’re hard to read and parse. They only tell us where something went wrong, not why it failed (the root cause).

Wrapping makes errors easier to locate:

func doSometing() ([]int, error) {
	i1, err := pkg.GetOne()
	if err != nil {
		return nil, errors.Wrap(err, "Err when get one")
	}

	i2, err := pkg.GetTwo()
	if err != nil {
		return nil, errors.Wrap(err, "Err when get two")
	}

	i3, err := pkg.GetThree()
	if err != nil {
		return nil, errors.Wrap(err, "Err when get three")
	}

	return []int{i1, i2, i3}, nil
}

Better error definitions:

  • Classify by severity level using logrus.Level
  • Classify by type so you can return just error types to clients while including more detailed custom errors (similar to stack info, error type, other data)
  • Add application-specific data
  • Make them searchable

I strongly recommend adding a custom error package for systems that need long-term maintenance. This separates system errors from user-level errors.

Return nil for no errors. Otherwise you might return an error that’s always non-nil!

Channels

Closed channels: closed channels can synchronize goroutines.

package main

import "sync"

func main() {
	var ch = make(chan struct{})
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            <-ch // wait for close(ch), all goroutines start together
            println(n)
            wg.Done()
        }(i)
    }

	close(ch)
    wg.Wait()
}

Rules:

  • Never close a channel on the receiving end
  • Close the same channel only once
  • Closing a nil channel panics
  • Declare parameters as unidirectional channels to constrain users

Send only: c chan<- T Receive only: c <-chan T

You cannot range over an uninitialized channel.

Design Principles

Interface-oriented programming: abstract core actions into interfaces. This decouples specific structures and forms. Specific structs should be package-private. Provide initialization functions that return interface-typed objects.