I have a service type called ComputeService which implements certain domain logic. The service itself depends on implementation of an interface called Computer which has a method Computer.Compute(args...) (value, error). As shown, Compute itself might return certain errors.
ComputeService needs to send appropriate errors from a set of domain-errors with proper domain-error code so that translations can be done and also clients can handle errors appropriately.
My question is, should the Computer implementations be wrapping their failure in domain-errors or should ComputeService do this. If ComputeService is the one doing it, then it will have to know about different errors returned by different implementations of Computer interface which in my opinion breaks the abstraction. Both ways are demonstrated below:
package arithmetic
type Computer struct {
}
func (ac Computer) Compute(args ....) (value, error) {
// errors is a domain-errors package defined in compute service project
return errors.NewDivideByZero()
}
OR
package compute
type Service struct {
}
func (svc Service) Process(args...) error {
computer := findComputerImplementation(args...)
val, err := computer.Compute(args...)
if err != nil {
if err == arith.ErrDivideByZero {
// converting an arithmetic computer implementation
// specific error to domain error
return errors.NewDivideByZero()
} else if err == algebra.ErrInvalidCoEfficient {
// converting an algebraic computer implementation
// specific error to domain error
return errors.NewBadInput()
}
// some new implementation was used and we have no idea
// what errors it could be returning. so we have to send
// a internal server error equivalent here
return errors.NewInternalError()
}
}
Implementors of Computer should respond with the domain errors, since they're the closest ones to the action and best able to determine what an error is. Like you said, having that logic in ComputeService breaks the abstraction. If you need mapping code from specific Computer errors to domain errors, create wrapper structs that separate the main logic from that error wrapping code.
To keep internal error context, just embed the original error in the domain error and make IsSpecificDomainError helpers.
type MyDomainError struct {
Err error
}
func NewMyDomainErr(err error) error {
return &MyDomainError{err}
}
func IsMyDomainError(e error) bool {
_, ok := err.(*MyDomainError)
return ok
}
To keep internal error context, just embed the original error in the domain error
This can use Wrapping errors, which are on their way for Go 1.13 (Q4 2019), from issue 29934, as detailed here.
err.Is():
As Russ Cox mentions:
I think we all agree that strings.Contains(err.Error(), "not found") is fragile code.
I hope we also agree that we'd prefer to see code like errors.Is(err, os.ErrNotExist).
But the point is that in many cases, it is essential to future evolution of a package to keep callers from depending on a particular error result satisfying errors.Is(err, os.ErrNotExist), even if that is the underlying cause in today's result.
It's like looking at an unexported field or comparing error text - it's a detail that might change.
And while strings.Contains looks and is fragile, errors.Is does not look nor should be considered fragile.
If we are to avoid it being fragile, then we need to provide a way for packages to report detail without letting clients test for it. That way is errors that can't be unwrapped.
err.As():
var pe *os.PathError
if errors.As(err, &pe) {
use(pe)
}
%w:
func inner() error { return errors.New("inner error") }
func outer() error { return fmt.Errorf("outer error: %w", inner()) }
fmt.Fprintf("%+v", outer())
// outer error:
// /path/to/file.go:123
// - inner error:
// /path/to/file.go:122
The current status for Go 1.13:
Just stating what I see as the compromise solution offered by the team:
fmt.Errorf is currently being used extensively to wrap errors and return a new (opaque) error (as in you cannot access the underlying error).
'%w' can now be used to explicitly opt-in to return an error that can be unwrapped.
errors is designed as a base package with no dependencies so that every package can depend on it.
the team agrees to punt on the areas that there is broad disagreement, and want to release just enough (errors.Is, errors.As, extension to way most folks wrap errors) so folks can achieve things.
Generics is not here yet, and we do not know when it will come: the heated discussion on that will make this one on "error 2 values" look like child's play.
errors.Is and errors.As are clean and concise enough to be comfortable for a long time.
Most of the contentious things have been punted to go 1.14.
Wrapf cannot live in errors as it is a base package.
Wrapf means team MUST decide on what happens when a nil error is passed: Punt on it.
Wrap may conflict with ideas being considered for localization, internationalization, etc.
ErrorFormatter and ErrorPrinter have not yet gotten much deeper usage and have warts. Punt.
Related
When working with go there is a pattern used to define errors and handle them in a (to me) very peculiar way. Often errors are declared like ErrorSomethingWentWrong = errors.New("Just an example!"). You can use errors.Is(err, ErrorSomethingWentWrong) to catch that specific error. The Is function can do this by comparing the pointers. But in order to make the comparison I need to know which variable name is used to define the errorString so I can use errors.Is to catch it.
For example:
ErrorSomethingWentWrong = errors.New("Just an example!")
func DoSomething() (*Something, error) {
return nil, ErrorSomethingWentWrong
}
I know a error is returned with the string "Just an example!" But I don't know it has the variable name ErrorSomethingWentWrong:
func checkError() {
if errors.Is(err, ErrorSomethingWentWrong){ // how to know this???
//handle error
}
}
When I use errors.Is(err, ErrorSomethingWentWrong) I can catch this error and handle it. When using debugging I can't see that the errorString represents the ErrorSomethingWentWrong variable. But when I don't know that the variable name was ErrorSomethingWentWrong I need to reverse engineer the code or read the docs to know which error is returned.
So how can you know using debugging or reflection to retrieve the error variable name?
If I understand correctly, this is your scenario:
You are receiving an error from a package, and there is no documentation to provide you with the errors you should be handling. You are debugging, and see an errors.errorString, but don't know if there is a global and public error value that you can compare to.
Unfortunately, this is a value and the debugger can't tell you if this is a global variable or if so which one, because you don't receive a variable, only a value.
You can:
read through the source code of the package you are calling and see which errors are being returned and how to handle them
search the source code for the text in the error string, this could help you pinpoint where the error is defined or created.
If it turns out the specific error you wish to handle is not defined globally (and re-writing the package is not feasible for your case), so you can't handle it. This can happen if somebody uses errors.New inside a function (bad practice). Then you can also try these:
func handleError(err error) {
if err.Error() == "full error string" {}
if strings.Contains(err.Error(), "partial error string") {}
}
But those are both ugly imho.
TL;DR It's not possible, you have to read the source code.
I am developing a CLI tool and in cases where the things go wrong, I want to log the custom error and exit with panic. The issue with panic is that an exit by panic is followed by the stack trace which I don't want to show to the user. Is there a way to panic and have a ninja-like stealthy/quiet exit?
(Choosing panic over os.Exit() since that would handle any defer and also seems a lot cleaner.)
Well, a direct answer is yes, there is a way:
panic with an error of a custom, "sentinel", type, or with a custom, "sentinel", value, then
Have a defer-red call in main which recover()-s the panic and checks whether the returned value is of a sentinel type (or equals to a sentinel value—the exact approach is up to you).
If it detects a "known" error, it silently exits with a non-zero exit code;
otherwise it panics again.
But honestly I think your mindset is too affected by programming languages with exceptions: I have yet to see a CLI app which would have any difficulty in handling errors "the usual way" or would actually benefit from bailing out using panic.
A counter-argument to the approach you're craving for is this: bubbling up an error allows adding more context to it on each level of the call stack being unwound, where it makes sense,—producing as useful as possible error to display.
Basically, it works like this:
func main() {
...
err := DoStuff()
if err != nil {
log.Fatal("failed to do stuff: ", err)
}
...
}
func DoStuff() error {
foo, err := InitializeWhatever()
if err != nil {
return fmt.Errorf("failed to inialize whatever: %w", err)
}
...
return nil
}
func InitializeWhatever() (*Whatever, error) {
handle, err := OpenWhateverElse()
if err != nil {
return nil, fmt.Errorf("failed to open whatever else: %w", err)
}
...
return whatever, nil
}
…which would produce something like
failed to do stuff: failed to inialize whatever: failed to open whatever else: task failed successfully
…which makes it crystal clear which sequence of events led to the undesired outcome.
Sure, as usually, YMMV and no one except you knows your situation best, but still it's something to ponder.
And here's an assorted list of thoughts on what I've written above.
The approach with panicking with a known value / error of a known type is actually nothing new—for instance, see net/http.ErrAbortHandler.
The encoding/json package used to employ this approach for breaking out of multiple nested loops (has been reworked since then, though).
Still, some experts consider sentinel errors to be bad design and recommend instead asserting (using Go's type assertions) behaviour of the errors—for instance, you can look at how net.Error has Timeout and Temporary methods which allows to not have concrete exported types for temporary errors, and errors due to timeouts, and their combinations,—but instead have them all support a common set of methods which allow the callers to make sense of the error's nature.
Since 1.13, Go gained advanced support for "wrapping" errors—producing matryoshka-style error chains, and this allows an approach to error handling which sits somewhere between the two extremes above: one can create an error of a particular type at the place an error was first found, then wrap it in contexts as they bubble up the call stack, and then assert the type of the innermost error at the place which handles the error. A friendly explanation on how it works.
Well, and I'd recommend reading this for, I must admit, had you already done this, you'd probably not ask your question in the first place ;-)
If a piece of code has its own error type as in
var ErrSomethingWentWrong = errors.New("Something went wrong"
I believe in my code I can do this
import github.com/therepo/theproject/thepackage/thatexportstheaboveerror
// code that might return the above error
if err == thatexportstheaboveerror.ErrSomethingWentWrong {
// handle the particular case
}
What happens when the error returned is via fmt.Errorf as in this case?
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
How can value (or type or whatever) check/assertion should be performed go-idiomatically?
Is this the only way around it?
if err != nil {
if strings.Contains(err.Error(), "unable to authenticate") {
// handle the particular error
}
}
Since Go version 1.13, the errors package has included the concept of "wrapping" errors. A routine returning a "wrapped" error calls fmt.Errorf using the %w verb in the format, to wrap an inner error into an outer error. An outer error can then be tested to see if it contains a particular inner error.
Prior to that, xerrors provided the same general concept.
Both of these require that whoever produces the error value use the provided wrapping interface. The routine you are asking about—part of https://godoc.org/golang.org/x/crypto/ssh—does not do this. Some older code that doesn't wrap errors provides certain kind of error-testing functions. For instance, when os.Open returns an error, os.IsNotExist will tell you if that error is because the file does not exist.
Unfortunately this particular package has no such test, so you're pretty much stuck with what you've suggested (direct string inspection) if you really want to know, programmatically, that this error came from this particular source.
In go I often use
func MyFunc(s someInterface) error {
err := OtherFunc(s)
return fmt.Errorf("something wrong: %s", err)
}
So I lose the original error value, because I just take the error string and forge it into a new error. That is what I mean with dynamic errors.
Now consider a test for MyFunc():
func TestMyFunc(t *testing.T) {
s := mockSomeInterface()
testErr := MyFunc(s)
if testErr != interfaceSpecificErrorValue {
t.Errorf("fail")
}
}
What would I use for interfaceSpecificErrorValue (which is specific to someInterface in this example)? Or how could I make this testable?
I understand that I can solve this by defining all my possible errors beforehand and give them a constant value. What I am interested in is if there is another way to achieve this, because I like the hierarchical error messages that you can dynamically build up using fmt.Errorf("...: %s, err). There must be a good way to keep the error hierarchy without losing the original value.
(Comparing the output of the Error() method is a possibility, but not a good one.)
My preliminary answer to this is: In Go currently there is no canonical way to achieve nested errors with comparable error values.
After reading the offical documents and blog posts on error handling again, quite some source code from standard libraries, and finally this proposal: https://github.com/golang/proposal/blob/master/design/go2draft-error-values-overview.md, I decided I will keep my error types simple and static and wait for Go 2 to offer a better way.
I'm trying to do something that seems like it should be trivial until I read up and now it seems like it should be really complex. ;-)
I've knocked up a test pattern to illustrate:
http://play.golang.org/p/Re88vJZvPT
At the most basic I'm trying to have a function that can read data from a channel and spit it out on another one. Easy. The test does this as long as you use the pusher function shown.
However the problem with this is that doing it this way I would need a different pusher function for each type of data I want to push through it.
Now I've done similar things in the past with an empty interface as nothing in the pusher code cares about what's in the data structure. What I can't figure out is when I'm dealing with channels of an un-cared-about data structure.
To illustrate the concept of what I'm trying to achieve please see the function pusher_naive_generic.
However that doesn't work either so more reading up implied the way to do it was making use of reflection and finally you see my pusher_reflect_generic function(obviously this won't achieve the same intended function as the others it's showing where I got to before getting stuck).
Which still fails because I can't get from an interface that contains a chan to the structure read from that chan.
Hopefully the code makes more sense of what I'm trying to achieve than my words actually do. I can make all of this work by explicitly coding for every type, but what I can't figure out how to do is code it for any future type.
If I have understood your question correctly, then this might be the solution:
http://play.golang.org/p/xiDO7xkoW4
func forwardChannel(i interface{}, o interface{}) {
it, ot := reflect.TypeOf(i), reflect.TypeOf(o)
if it != ot {
panic("forwardChannel: both arguments must be of the same type")
}
iv, ov := reflect.ValueOf(i), reflect.ValueOf(o)
for {
v, k := iv.Recv()
if !k {
break
}
ov.Send(v)
}
ov.Close()
}
Note that Recv, Send and Close panic if i and o are not channels.