I understand that error
is an interface, and that in theory there are lots of choices for what to satisfy that interface. However, in practice, what tends to be chosen?
When is errors.New
or fmt.Errorf
preferred? Should those be the default, in the absence of additional requirements? Should values from those be reused, or should those calls be reinvoked every time? What about when there needs to be additional data attached to an error? When is fmt.Errorf
sufficient, vs when is something custom like a struct necessary?
Suppose function A calls function B, function B returns an error, and function A is unable to resolve this error and must itself return an error. Should function A return the same error, the same error but wrapped, or a new error entirely? In the case of wrapping, how should this be structured to be more easily handled by further up the call stack?
How should errors be checked? In particular, what if a function needs more complex logic beyond just if err != nil
? How is the recommended way to differentiate between error types and potentially destructure error values into their components? Is it considered good practice to use a series of type assertions on the possible types, or would that be too much of an abstraction leak?
Sorry if that was too dense a wall of text. I was just trying to get a train of thought written down.
Thank you in advance for your help.
评论:
shovelpost:
gopher8:When is errors.New or fmt.Errorf preferred? Should those be the default, in the absence of additional requirements? Should values from those be reused, or should those calls be reinvoked every time? What about when there needs to be additional data attached to an error?
var ErrEmailNotValid = errors.New("email not valid") if err != nil { return fmt.Errorf("error opening database: %v", err) }
When is fmt.Errorf sufficient, vs when is something custom like a struct necessary?
If you need your errors to carry more data that you need to inspect later then you define a custom struct that usually satisfies the
Error()
method. Read this part.Suppose function A calls function B, function B returns an error, and function A is unable to resolve this error and must itself return an error. Should function A return the same error, the same error but wrapped, or a new error entirely? In the case of wrapping, how should this be structured to be more easily handled by further up the call stack?
It really depends. Does function A know any additional information or context to wrap the error around? If it does then you can wrap the error with
fmt.Errorf
to provide that additional context. Need more fine grained wrapping? Use a custom error struct.How should errors be checked? In particular, what if a function needs more complex logic beyond just if err != nil? How is the recommended way to differentiate between error types and potentially destructure error values into their components? Is it considered good practice to use a series of type assertions on the possible types, or would that be too much of an abstraction leak?
That's a very broad question and again it depends. A very useful technique is one that uses type assertions in just once place in the code to determine what kind of error it is and handle it appropriately. Read this part.
If you are writing a library and you are concerned about the surface of error variables you make public (which the clients will have to use), there's another useful technique. Read the "Assert errors for behaviour, not type" part in this article.
Also check how the Upspin team does error handling. I am not saying you should do that but it shows what the Go proverb "Errors are values" means in practice. Program with your errors to handle your specific case.
kavirajk:Some thoughts on wrapping errors and more: https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package
This pkg looks good. Best practices towards go2
