-
Notifications
You must be signed in to change notification settings - Fork 4
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
Strictness with parametrized errors #10
Comments
As a subquestion, can we somehow disable this particular dynamic error check, while keeping the |
Actually, it is:
|
Not to be contrary, but not all returned errors should be wrapped. What you gave as an example is a sentinel error much like Case 1: Issuing In your example, that stack trace will also be unusable as it would be recorded at ErrProviderUnknown declaration. Case 2: A common implementation for
So, the cleaner version of providing errors is: const (
ErrUnknownStorageProvider = "unknown storage provider: %s ..."
) Usage: But more to the point, creating a dynamic error, like done in example here, with or without parameters, isn't discouraged in Go 1.13+, nor should it be discouraged in the err113 check. What you essentially did is deprecate errors.New outside of global context, and any non-wrapped error (e.g. original error) which would be function scoped, without a variable declaration. Please consider some exceptions here. Using |
are you sure? AFAIK the stacktrace will be recorded at
This is what I'm fighting against :) There was no standard way to analyse an error prior to 1.13. So people were doing strange and dangerous things like analyse the error messages. This must be stopped! With a method I've described above any package can:
p3 is not possible with an error just created at the return point, is it? this is why I think we should never create new errors itself nowadays but use wrapping everywhere. |
Standard library doesn't record stack traces at all. Only pkg/errors does. The main reason behind that is that the standard library is more performance oriented, and there's a penalty if stack traces would be recorded for every error. That's why with pkg/errors, a newly returned error must go either over errors.New, Errorf, or Wrap, to add the stack trace on the leaf function. At some point, the stdlib may add similar functionality, with similar constraints - stack traces could be effectively free if they can be recorded at compile time, but that's guesswork for a future go release. Not every error is meant to be handled, some are meant as an user guide. Unfortunately that means that the error is basically returned as a string, possibly including non-standard formatting, newlines and other formatting directives like ansi terminal colors. Having a base error (e.g. your 2. point) is actually working against this intent. There's also a little more than 500 examples of fmt.Errorf usage in the standard library, which would be reported as errors. fmt.Errorf search in golang/go. An especially painful example are unit tests, where usually the expected and returned values are printed if they don't match. Like: return fmt.Errorf("different number of files: %d != %d", len(p.files), len(q.files)) Of course, just excluding packages that import testing also isn't enough of an approach, as there are often examples of local packages to help with testing that then return errors in the same way. Not to mention that the same rules should apply for code in tests, as anywhere else. I think as far as fmt.Errorf goes, the behaviour should be as close as possible to fatih/errwrap, meaning no error should be reported if no error parameters are passed to fmt.Errorf. I don't have a strong opinion on errors.New, but enforcing error values there instead of string literals should be fine. The pkg/errors package is a special case as it won't have |
I'm a little bit confused, sorry what should we do with this ticket? I've created this linter to push the strategy I believe in: every error returned must be analyzable with in case of dynamic errors so this does not make sense for me to have these checks applied separately. does it make sense to you? |
With tests and cli apps, the error path is never checked with anything else
than != nil, before landing in log.Fatal, t.Fatal/Error; not all errors
need to be sentinel errors.
It would make more sense to me to forbid err.Err() calls producing a string
(assuming somebody matches it), forbid passing it to print functions with
%s (only %+v is valid?), only allow %w on Errorf. What you describe is only
valid for importable packages where errors are meant to be handled, not
just logged.
As you see, there are edge cases. So yes, ideally, the checks would be
applied separately, or would at least be partially configurable.
Gosec which detects os/exec calls has a particular comment to remove single
errors, but as Errorf usage may be more widely used (the drone ci file I
linked has 5+ examples in a single file), the preffered way would be to
ignore such errors in the complete file, or fully disable the check for
Errorf. There’s no reason to ever disable errors.Is() checks.
…On Sat, 16 May 2020 at 00:22, Daniel Podolsky ***@***.***> wrote:
I'm a little bit confused, sorry
what should we do with this ticket?
I've created this linter to push the strategy I believe in: every error
returned must be analyzable with errors.Is() (or even errors.As(), but
it's completely different story).
in case of dynamic errors errors.Is() become useless, doesn't it?
so this does not make sense for me to have these checks applied separately.
does it make sense to you?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#10 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABY7EF2FSZTY2VSMLCDC2DRRW6DRANCNFSM4M5YBSPQ>
.
|
Just to add my current work-around, using issues:
exclude-rules:
- path: _test\.go
linters:
- goerr113
- linters:
- goerr113
text: "do not define dynamic errors" I could probably do without excluding _test.go for my use cases, and I'd have to do some work figuring out how to include goerr113 in a specific path. Having a specific error code would be nicer to exclude, for example: issues:
exclude:
- GE002 |
definitely looks like a solution can we close this issue now? |
I'm not such a fan of using package global variables because not only should we avoid using global variables, there is another Perhaps we could lead towards implementing the const errProviderUnknownMessage = "unknown storage provider"
// ErrProviderUnknown is an unknown provider error.
type ErrProviderUnknown struct {}
// Error satisfies the error interface for ErrProviderUnknown.
func (err *ErrProviderUnknown) Error() string {
return errProviderUnknownMessage
}
func someFunc(providerName string) error {
return fmt.Errorf("%s: %w", providerName, &ErrProviderUnknown{})
} |
package-level errors are not variables, they are semi-constant I do not see any reason to protect them, but, in case we need the really const errors we can go this way:
|
type ConstError string
func (e ConstError) Error() string {
return string(e)
}
const ErrSomethig ConstError = "some const error" I like this even better than the empty struct! |
@onokonem i'm fine closing this, i'm even better if you'd be willing to add error codes which we could match against if needed (e.g. GE001 for errors.Is checks, GE002 for Errorf...). After all, I'm currently matching against text from the error, and having a consistent error code would help with that. |
Often, errors require parameters, e.g.:
These aren't errors that need a statically/globally defined type, nor is there an error available which we can wrap. Both errors.New and fmt.Errorf report errors with err113, but there isn't a clear way to resolve them. Should the error be reported, if we're not passing an
error
typed parameter for%w
?The text was updated successfully, but these errors were encountered: