This is not an exhaustive list, just off the top of my head
- nil reference
- nil map writes
- array or slice out of bounds
- divide by zero
- type assertion failure
- closing a closed channel
- sending to a closed channel
- That list are probably the major areas
- If you keep that in the back of your mind it will take you a long way
- If you are in that kind of area, think about what does recovery look like?
- something like gin has recovery middleware builtin,
- If you are not in the handler goroutine, recovery is on you
- If you are in a handler goroutine, it may be worth adding an error boundary regardless
func FlawlessFunc() {
defer func() {
if err := recover(); err != nil {
// handle it
}
}()
}- if a panic happens in a goroutine, it will nuke the goroutine and not recover
- this was a factor in the recent outage
- Be very careful when dereferencing array elements by index
- Be careful when dividing numbers
- Use safe type assertions
- Be careful when working with channels
- Avoid nil
- Only pointers can be nil
- Nil is dangerous
- Because of that, we should avoid pointers unless there is a good reason
- If you don’t understand the nuance here, don’t worry about it too much
- Default to using values, not pointers
- copying a pointer to the heap is cheap
- heap memory allocation is expensive (relatively)
- heap garbage collection is expensive (relatively)
- stack variable deallocation is cheap (relatively)
- there is a sweet spot where values are cheaper then pointers
- hard to know where, less then cpu cache size is safely cheaper
- however, none of that matters unless you have a measurement that shows it matters
- If you have megabytes of data, use the heap for performance
- If you have 25 small values on a struct, you are probably optimizing for immeasurably small benefits
- Sometimes its not immeasurable, but that needs to be backed up with data
- this should be rare, but certain cases are common
- e.g. json unmarshall
- Don’t work with a pointer in the parent function when possible, create a pointer to your value at the point of passing
var mutatable Mutatable FuncThatMutates(&mutatable) // pointer created here!
- pointing to a large amount of data
- micro-optimization + measurement and target goal
- like a socket, probably made through a third party library
- This is a good use case for something like ErrNotFound as a signal of the “return state” of a function
- Errors are values that you can query for type and use for flow control
- Errors allow delegation of how to handle a condition to a parent function
- Errors are accumulators for information on how something may have gone wrong
- Errors are signals for the state of a function return
- errors.Is(err, ErrSentinal)
- errors are chains of values, a query is for any element of the chain
- good to start chains with static, public vars, so they can be queried
- an error should only be logged once, at the point in the code where it is handled
- if an error is kicked up the call stack, it should be wrapped
- %w is the sprintf token to know for wrapping
- only wrap if you can add useful information
- if you do not wrap, ask yourself if the current function you are in should exist, or you are splitting stuff up too much
if err != nil { return fmt.Errorf("finding widget %s in location %d: %w", widget.Code, location.Id, err) }