Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

variadic Wrap #64

Closed
powerman opened this issue Jun 27, 2016 · 7 comments
Closed

variadic Wrap #64

powerman opened this issue Jun 27, 2016 · 7 comments

Comments

@powerman
Copy link

Example in http://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package

f, err := os.Open(path)
if err != nil {
        return errors.Wrapf(err, "failed to open %q", path)
}

is actually good example why Wrap() should be variadic - os.PathError already contains Op and Path, so there is no sense to add same thing once again, all we need here is add stack details.

Another example is jsonrpc2.Error type. It is somewhat unusual because it's Error() returns error details serialized into JSON (this is needed to pass thru net/rpc internals while keeping error details). So, if we do errors.Wrap(jsonrpc2_err_here, "") and then net/rpc will internally call Error() then we'll get ": {…json here…}" and that's extra ": " prefix will break restoring error details when it comes out from net/rpc.

Providing variadic errors.Wrap(err) which didn't add any prefix will solve both issues.

@davecheney
Copy link
Member

Thank you for raising this issue. There is a lot here so let me tackle the
low hanging fruit first.

The way jsonrpc.Error2 works sounds terrible. Not only do they rely on a
specific error value, but actually the output from the Error method being
valid JSON. That means their errors cannot be wrapped.

With that said I agree that even in cases where you only wrap once to
annotate an error from the standard library there can be cases where the
message is redundant.

If we made Wrap(err error, ...string) error this would address that
problem. I agree.

My concern is how this could be misused. Every time you wrap a new stack
trace is captured, so people wrapping aggressively would receive a
craptonne of stack trace output which hopefully is a forcing function to
improve.

My other concern is because wrap becomes easier to use, you don't have to
think up a meaningful message, that people will wrap all the time, just in
case.

I guess I'm saying that while I see the specific problem with errors from
the OS package, which are well designed, I'm not certain that the problem
is so widespread that changing the signature of Wrap to address this use
case will pay for the downsides of making Wrap easier to misuse.

On Tue, 28 Jun 2016, 04:56 Alex Efros notifications@github.com wrote:

Example in
http://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package

f, err := os.Open(path)if err != nil {
return errors.Wrapf(err, "failed to open %q", path)
}

is actually good example why Wrap() should be variadic - os.PathError
already contains Op and Path, so there is no sense to add same thing once
again, all we need here is add stack details.

Another example is jsonrpc2.Error type. It is somewhat unusual because
it's Error() returns error details serialized into JSON (this is needed to
pass thru net/rpc internals while keeping error details). So, if we do errors.Wrap(jsonrpc2_err_here,
"") and then net/rpc will internally call Error() then we'll get ":
{…json here…}" and that's extra ": " prefix will break restoring error
details when it comes out from net/rpc.

Providing variadic errors.Wrap(err) which didn't add any prefix will
solve both issues.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#64, or mute the thread
https://github.com/notifications/unsubscribe/AAAcA2e_iBOBRTa_ystDhj6vdSSxZ_Frks5qQBzJgaJpZM4I_ZbD
.

@powerman
Copy link
Author

The way jsonrpc.Error2 works sounds terrible.

Yep, it is. But that's just to work around real issue at https://golang.org/src/net/rpc/server.go#L388 - handlers for JSON-RPC 2.0 should be able to return errors with code, message and optional data, as required by protocol. But net/rpc will convert returned by RPC handler error to string, effectively destroying rich error data type. If you've any idea how to handle this issue in better way…

My other concern is because wrap becomes easier to use, you don't have to think up a meaningful message, that people will wrap all the time, just in case.

I'm not sure I understand the difference between these cases.
In first case you're used (your package's) errors.New("msg1") and get "msg1" with stack - and as you've said in that blog post that's enough in most cases and no more extra Wrap() is needed.
In second case you've get result of (stdlib package's) errors.New("msg2") from 3rd-party package without stack, used errors.Wrap(err) to add stack to it and get "msg2" with stack.
What makes you think "msg2" isn't as good as "msg1" and must be enriched by extra Wrap's message?

Every time you wrap a new stack trace is captured, so people wrapping aggressively would receive a craptonne of stack trace output which hopefully is a forcing function to improve.

Why do you think people will wrap aggressively? If they'll use your package, then they'll use it in their own code and thus didn't need to use Wrap at all (unless they really needs to add extra description), with only exceptions for places when they get errors from 3rd-party packages.

Maybe real issue is in Wrap doing two different things - add description and stack trace, while there are cases when only one (any of them) of these things is actually wanted?

receive a craptonne of stack trace output which hopefully is a forcing function to improve

At a glance you can skip adding stack trace if you detect wrapped error already has one. Or skip in case you detect current stack is a subset of already existing. But I didn't checked the code, so this may be irrelevant, sorry if this is the case.

@davecheney
Copy link
Member

davecheney commented Jun 28, 2016

Yep, it is. But that's just to work around real issue at https://golang.org/src/net/rpc/server.go#L388 - handlers for JSON-RPC 2.0 should be able to return errors with code, message and optional data, as required by protocol. But net/rpc will convert returned by RPC handler error to string, effectively destroying rich error data type. If you've any idea how to handle this issue in better way…

I'm sorry about that, I don't think this package can help. You'll have to not wrap errors produced in this path.

In second case you've get result of (stdlib package's) errors.New("msg2") from 3rd-party package without stack, used errors.Wrap(err) to add stack to it and get "msg2" with stack.
What makes you think "msg2" isn't as good as "msg1" and must be enriched by extra Wrap's message?

My thinking is you should use errors.Wrap when you want to add context to an error; what were you doing when the error occurred.

Why do you think people will wrap aggressively? If they'll use your package, then they'll use it in their own code and thus didn't need to use Wrap at all (unless they really needs to add extra description), with only exceptions for places when they get errors from 3rd-party packages.

Yes, the hope is that if the code is within their own projects, they'll just use errors.New or errors.Errorf at the point error happened and that will be all anyone needs to do. Everywhere between the point the error happened and the point it's handled (at the top of the goroutine or in main) will just do

if err != nil {
        // handle cleanup
        return err
}

It is true that the use of Wrap has become less important since I realised we can just capture the full stack trace at the point error happens.

This is part of the recommendations (in the "putting it all together" section) that I wrote about here, http://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package.

Why do you think people will wrap aggressively? If they'll use your package, then they'll use it in their own code and thus didn't need to use Wrap at all (unless they really needs to add extra description), with only exceptions for places when they get errors from 3rd-party packages.

Because errors.Wrap would become easier to use because you don't need to consider the message to attach. I believe that it will become rote and people will do it "just in case".

By requiring that Wrap takes a message, it requires you to think about what you want to say at that point, and makes anti patterns like errors.Wrap(err, "") stand out.

Maybe real issue is in Wrap doing two different things - add description and stack trace, while there are cases when only one (any of them) of these things is actually wanted?

I agree with you and I talked about this in the blog post above. My concern was if Wrap became variadic it would have two unrelated responsibilities; Wrap an error with a stack trace, and Wrap an error an extra message to take context.

With that said, I can see that this argument could be applied equally in the opposite direction as you have done. We could say "Wrap returns an error that wraps the provided error, returning a new error that records the stack trace at which Wrap was called. Wrap additionally adds an optional message for context".

As I said above, I see the utility of this, but I worry about misuse of this and anti patterns. Those use cases are as important to me as the use cases for adding this feature.

At a glance you can skip adding stack trace if you detect wrapped error already has one.

I think there are legitimate use cases for recording the full stack trace every time one of the four functions in this package are called. Displaying or suppressing that information is the responsibility of the thing that consumes the error, it should not be something the code Wrapping or generating the error should concern itself with.

@isadon
Copy link

isadon commented Aug 19, 2016

Implementing the errors package for the first time in my project led to me to instinctively attempt to make the Wrap function variadic, as plenty of times I was returning standard lib errors but didn't want to add a context message. I didn't see the need to add a context message if the stack trace would give me enough info about the error. However what you mentioned above are definitely very valid concerns. Making Wrap variadic will make it too easy for the programmer to misuse it eventually leading to over wrapping of errors and thus over capturing of stack traces. The user will most likely do this knowing and unknowingly.

As a solution why not have the Wrap function be variadic and generate the stack trace on the only on the first wrap of the error. Subsequent wraps of an error already wrapped would not append another stack trace - there is no point in appending a lower level stack trace when we have a higher/deeper one. Subsequent wraps however can definitely append another context message, but never an additional stack trace.

@davecheney
Copy link
Member

davecheney commented Aug 22, 2016

Have a look at #76 and and #81

@powerman
Copy link
Author

#76 is surely looks better than variadic Wrap.

@davecheney
Copy link
Member

I'm going to merge this into #76 and close this issue.

# for free to subscribe to this conversation on GitHub. Already have an account? #.
Projects
None yet
Development

No branches or pull requests

3 participants