Golang doesn't have try/catch blocks, so you can't simply "throw" an error and hope something will catch it and give you a stack-trace showing you where it came from. Instead we return errors and check them ... but it's important to introduce context before returning ... here's why
Here a simple go program I wrote which doesn't appear to work 🤔
$ go run main.go
access denied
... it attempts to "process" and "save", but since the "process" function adds no context to the error (it just bubbles it up), the final error is very confusing!
Here's the code:
package main
import (
"strings"
"fmt"
"github.com/pkg/errors"
)
func main() {
err := process("Hello, World!")
if err != nil {
fmt.Println(err) // log errors
}
}
func process(words string) error {
// process
data := strings.ToUpper(words)
// attempt to persist
err := persist(data)
if err != nil {
return err
}
return nil
}
func persist(data string) error {
return errors.New("access denied")
}
This can be resolved with a single change -- using errors.Wrap()
to give context to our underlying errors:
func process(words string) error {
...
// return err
return errors.Wrap(err, "failed to persist") // <--- wrap in context: "what it failed to do"
...
}
Now we get a more context-aware error, and if this was in a log i would know what the program was doing when it failed -- it was trying to persist
$ go run main.go
failed to persist: access denied
The exception to this is if you're intentially trying to avoid leaking your underlying implementation details