-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle errors once #179
Handle errors once #179
Conversation
Adds new guidance on how handling errors once rather than double handling, e.g. log-and-return. Resolves uber-go#65
The other note I like, by wrapping errors you are able to specifically test you are hitting that wrapped case. If you have a module that just passes through errors, future changes may cause those paths to change, and you to lose test coverage. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far. I'm split on whether it's worth discussing error boundaries (at a high level), maybe through examples (specifically thinking of your degradation examples).
The general idea being that some confusion around error handling may stem from not really understanding what should really "care" about an error (i.e., what should that error's terminal handler be), and examples that help folks think about how to model that (through the lens of error boundaries/SOC) may help as well.
Thoughts?
Could you clarify what you're imagining here, @mway? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I like it. Though it may get a little blurry in terms of what errors are ok to handle and what aren't in the Match the error and degrade gracefully
section without some kind of guidance on error boundaries as @mway suggested.
For instance suppose someone has code similar to the example, except getUser
is an external pkg:
u, err := someClient.getUser(id)
Then does one match for canonical error types defined by someClient
and nothing else, like:
if errors.Is(err, someClient.ErrUserInactive) {
// ...
}
, or does one check for any kind of commonly-occurring errors returned from someClient.getUser
regardless of whether the error is wrapped or not
if errors.Is(err, fs.PathError) {
// ...
}
Okay, yeah, this is where the confusion might be coming from. I'll see if there's a way to expand the match errors section. |
Reorders the list of ways an error can be handled, going from most specific to least specific, and makes the examples more realistic for those cases.
@mway @sywhang Updated to try to clarify error matching more. I also want to clarify my intent here to help future discussion. If you think that that intent is getting lost because we enumerate some ways of handling errors, I'm happy to pare down the entry and drop those extraneous details. |
src/error-once.md
Outdated
@@ -0,0 +1,112 @@ | |||
# Handle Errors Once | |||
|
|||
When a function receives an error from a caller, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how does a function receive an error from a caller or am I being stupid here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦 oops. yeah, callee.
src/error-once.md
Outdated
Regardless of how the error is handled, generally, | ||
a function should handle an error only once. | ||
It should not, for example, log the error and then return it again | ||
because its callers will likely handle the error too. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, by handle you mean "take an action", where action 1 is log, and action 2 is return.
or you mean "a function should touch a received error only once."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant "take an action". Let me reword a tad bit.
Related to uber-go/guide#179 Callsites that receive an error should either log, or return an error. However, if the callsite has additioanl context, the simplest option is to add it to the error, but it's then flattened into a string, losing the benefit of structured logging. This often results in callsites logging with additional fields, and returning an error that is likely to be logged again. `WrapError` provides a way for callsites to return an error that includes fields to be logged, which will be added to an `errorFields` key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on the guidance
log.Printf("Could not get user %q: %v", id, err) | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(tangentially related)
I often see this with structured logging when you want id as a separate field, but returning an error forces you to flatten the id into the error string, or you have to add an additional return of what fields to log (making it much more complex)
I've been meaning to solve it for a while, and think it can be solved relatively simply,
uber-go/zap#1271
src/error-once.md
Outdated
|
||
These include, but not are limited to: | ||
|
||
- if the function contract defines specific errors, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: function can refer to the caller or callee, what do you think of explicitly using those terms throughout? "if the callee function defines specific errors"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh good call. will change.
Related to uber-go/guide#179 Callsites that receive an error should either log, or return an error. However, if the callsite has additioanl context, the simplest option is to add it to the error, but it's then flattened into a string, losing the benefit of structured logging. This often results in callsites logging with additional fields, and returning an error that is likely to be logged again. `WrapError` provides a way for callsites to return an error that includes fields to be logged, which will be added to an `errorFields` key.
Related to uber-go/guide#179 Callsites that receive an error should either log, or return an error. However, if the callsite has additioanl context, the simplest option is to add it to the error, but it's then flattened into a string, losing the benefit of structured logging. This often results in callsites logging with additional fields, and returning an error that is likely to be logged again. `WrapError` provides a way for callsites to return an error that includes fields to be logged, which will be added to an `errorFields` key.
Related to uber-go/guide#179 Callsites that receive an error should either log, or return an error. However, if the callsite has additioanl context, the simplest option is to add it to the error, but it's then flattened into a string, losing the benefit of structured logging. This often results in callsites logging with additional fields, and returning an error that is likely to be logged again. `WrapError` provides a way for callsites to return an error that includes fields to be logged, which will be added to an `errorFields` key.
Related to uber-go/guide#179 Callsites that receive an error should either log, or return an error. However, if the callsite has additioanl context, the simplest option is to add it to the error, but it's then flattened into a string, losing the benefit of structured logging. This often results in callsites logging with additional fields, and returning an error that is likely to be logged again. `WrapError` provides a way for callsites to return an error that includes fields to be logged, which will be added to an `errorFields` key.
Related to uber-go/guide#179 Callsites that receive an error should either log, or return an error. However, if the callsite has additioanl context, the simplest option is to add it to the error, but it's then flattened into a string, losing the benefit of structured logging. This often results in callsites logging with additional fields, and returning an error that is likely to be logged again. `WrapError` provides a way for callsites to return an error that includes fields to be logged, which will be added to an `errorFields` key.
Instead of "the function" everywhere, clarify caller/callee relationships.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, thanks @abhinav! LGTM modulo a couple quick suggestions.
Co-authored-by: Matt Way <mway@users.noreply.github.com>
Co-authored-by: Matt Way <mway@users.noreply.github.com>
Thanks for the reviews, folks. Will add a changelog entry before merging. |
Adds new guidance on how handling errors once
rather than double handling, e.g. log-and-return.
Resolves #65
Preview