Two cases to consider come to mind. First, the common pattern
result, err := SomeFunc()
if err != nil {
return err
}
Here the code is just passing along the error to the caller, unchanged.Second, signaling errors ab initio
result := // some calculation or behavior
if result != expected
return fmt.Errorf("An error happened. Expected %v, go %v", expected, result)
In the first case, the discussions around whether or not to throw custom exceptions applies analogously: should you wrap the error or not?The second case, I argue, is always wrong. The error is "stringly typed", and can be examined and read by a person, but that's it. The correct way is to define an error type meaningful for the context. Errors in Go are type implementing the error interface
type error interface {
Error() string
}
therefore, an error should be a type relevant or the context. A useful starting point looks something like type DomainError struct {
Code DomainErrorCode
Message string
Details []DomainType
}
func (d DomainError) Error() string {
return Message
}
then code can look like // some work
if result != expected {
return DomainError {
Status: AnErrorCode
Message: fmt.Sprintf("Error %v. Expected %v, go %v", AnErrorCode expected, result)
Details: []DomainType{ expected, result }
}
}
And the caller gets back a type providing useful information.