-
Notifications
You must be signed in to change notification settings - Fork 17.4k
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
proposal: leave "if err != nil" alone? #32825
Comments
I second this. I really like how decorating every error before returning it adds human readable documentation to the source (usually we format our errors as "could not [what I am doing in these lines of code]: [previous error]") and also to the users reading errors. Errors generated this way are extremely informative and much easier to read than stack traces. Printed errors that include stack traces usually assume you have ready access to sources (administrators might not have such access) and actually know your way in the code. Errors without any form of context or tracing (the bare string "EOF") are absolutely useless. I think having shortcuts that make it easier to return naked errors will make Go programs print a lot of useless errors. If anything, we should push and support decorating errors with context, maybe with new vet and lint rules. |
I also like the explicitly error check. I think that instead of rethinking errors, we could try an alternative approach to make these checks shorter. Here's an example which I don't necessarily agree: value, err := foo()
return err if err != nil This would allow an shorter but still explicit approach. And it'd allow adding context! That said, inline ifs are a Ruby thing and don't feel very Goish, but this is just brainstorming. Maybe we find something else. EDIT: I added a proposal for this here: #32860 |
there should be only one way of doing a thing |
I think it's fair to say that we all know the answer to this. You need only go read one of the various proposals to find out the answer if you sincerely don't know. IMO, there's too little detail here for us to have a focused discussion (i.e. I don't think it qualifies as a proposal) and it will soon turn into another bike-shed full of circle-jerking and ideas that make the code less readable. |
So much this. |
Arguably I got into Go because of this explicit error handling. It sits somewhere between implicit try-catch that many languages go for and function types like Option or Maybe, which favors being returned to the user and be handled explicitly. I'm not sure if a new construct would really solve this. If you wrapped func handleErr(err error, cb func(error)) {
if err := nil {
cb(err)
}
} But the issue that makes this helper function less generally useful is the type system, which is a different topic. |
I second this. Adding context via defer does not make sense either, since we want to return different error messages to distinguish the different kind of errors. A |
Frankly, I don't want an implicit return that type Result<T> interface {
Expect(err error) T
OrElse(defaultValue T) T
}
func From<T>(value T, err error) Result<T> { ... } To me, this is a lot cleaner than the builtin currently being proposed, although further changes would be required to the above since you'd have a proliferation of methods that returned (value, error) and Result |
The current |
It might not make sense to change it, because the wrong problem is trying to be solved. The code that we are familiar with is not error handling. if err != nil {
return err
} This is error If I were to demonstrate this in a different language, Ruby. begin
some_method_that_raises_an_error
rescue => e # catch any exception
retry e # throw it up again
end This relays the same behavior as the golang code. When we detect that an exception occurred and then reraise it. We just throw it up the stack. In golang, we Where is the actual error handling occurring? We've all had similar experiences of the failure of this pattern. For example, receiving a This is why I believe the I've see |
I support keeping err!=nil check as is. |
Every time I dig into a sizable Go code base I ask myself how I might reduce some of the boilerplate. I always come back to:
|
The issue tracker is useful for many things, but one thing it is not useful for is a detailed discussion of a complex topic. The issue tracker provides no threading, and replying to a specific message is awkward. Since there is no actual proposal here, just a response to other proposals, I really strongly encourage you to take this discussion to the golang-nuts mailing list. |
If I may, I believe this is the answer. This new error proposal is in direct conflict with the goals of the language. The reason I love golang is because of its simplicity and clear use of control flow. One of the things I despise most about Java is the try throw construct. It's so disgusting. It encourages terrible error handling. Sending exceptions up the call stack is a horrible and disgusting method of handling control flow. On top, it encourages wrapping everything in a giant check and calling it a day instead of a self documenting and explicit handling of each error situation. If err != nil encourages good error handling, is self documenting and encourages good documentation as to the specific case, and it's honestly one of the things I love most about go. Making this new control flow interrupt, using messy, somewhat ambiguous returns and parameters, and confusing semantics is not in the spirit of the language I've come to adore. Verbosity is not a bad thing. Unnecessary verbosity is, but I'd argue that go's error handling is not unnecessary. It's part of the language's charm. |
Couldn't agree more. The explicit error handling is one of the best features of the language IMO. I always feel like many who are bothered by it just aren't used to it yet. |
It is not good for the issues are separated, but I'm thinking that two opinions are merged as one opinion in this case.
GitHub vote icons can not interpret the second. |
The explicit error handling in go is one of the reasons why I love golang. I don't understand why any go developer would want it any other way. I think the proposal to add new syntax is mostly from people comfortable using syntax used in other languages. it may take some getting used to but it works perfectly once you get use to it. |
I wrote #32811 and I support this proposal more... I'd rather just leave error handling alone. I think the emoji reactions to this proposal say a lot. |
I personally agree with leaving err handling as it is. One of things I like about Go is that the language is minimal, and generally speaking has one way of doing things. By adding new syntax for error handling, we’ll create a world where x% of code uses the current method, and y% uses the new method. This will, among other issues already discussed, create inconsistent code bases. I personally don’t think the value of new error handling syntax is worth the trade offs, since I consider the existing syntax enough/sufficient. |
As someone that is newer to Golang, one of the things that I find refreshing about the language is the explicit error handling. I've worked in Java, Ruby, Python, and Node pretty heavily, and dealing with errors is so much more onerous than in Go. I would rather see the clear 'path' of errors, than have it implied to me by some language construct that makes it more vague. |
ˋreturn ... if ...ˋ suggestion from @andreynering is actually fairly smart imho. Keeps the code explicit (no hidden control flow break) while cutting down the boilerplate (one-liner). |
Agree, leave |
I prefer the current format. It is clear and an easy pattern to teach. Bringing new engineers up to speed is simple as they can learn one simple pattern and repeat it. It also asks the users to at least consider the error in the current context, ensuring that at least the engineer is acknowledging an error can occur here and I need to think about what to do. |
I wrote #32804 and I would much rather see things NOT change. If your code is long, its because it does a lot of stuff. If you have a lot of error handling code, it's because you're doing a good job of handling all your cases. Please, lets not add things just for the sake of adding things. |
I enjoy the simplicity of the error handling as is. Expect is just an anagram for except, and I'd rather not use it. Thanks for starting this. |
Please don't change my holy grail. |
There was overwhelming community feedback requesting more streamlined error handling (from the annual survey). The Go Team is now addressing that issue. |
@icholy Sure, but the current proposals leave a lot to be desired. They all seem to either obfuscate the error handling, revert to more try/catch/finally style implementations, bubble the error handling up out of context, or otherwise make it more complicated. Since Go is supposed to be a simple language, I think a lot of us were hoping for a simple option. I haven't seen any I personally like, so I think that the better option is to keep the current pattern. One complaint was having to type it, but virtually every editor has shortcuts to insert code snippets, so it really isn't a big deal. Perhaps it is my own experience having used Go since pre 1.0, but I happen to like the simplicity and don't mind the redundancy. |
This is a proposal about the way gofmt currently formats if err != nil (This is not an opinion about the try() proposal.) When an if statement returns a one-line not-nil error value, such as:
gofmt could relax its own if-statement rule and format it on one line like this:
Three lines of error handling code becomes just one line. Less clutter. Easier to follow program flow. There will need to be some judgement about where to draw the line (pun acknowledged) with this
But elaborate multi-line error handling should remain as it is: multi-line and clear and explicit. |
The try proposal has been withdrawn: #32437 (comment) Generics anyone? |
I have tried that, imho the code is even more unreadable that way than with multi line formatting. try is much better than that solution. |
IMO the problem here is rather not how the error handling is performed, but whether it is ignored. Wouldn't it be possible to leave the |
Many people want a linter showing ignored errors. |
I'd prefer making this a hard error, but looking at the tons of already written legacy, linter is fair as well. |
i find https://github.com/kisielk/errcheck valuable for telling me about unhandled errors @plyhun @sorenvonsarvort |
As seen in the discussion on #32437, this proposal has in effect been accepted for now. Closing. If the issue arises again, a new proposal can be opened. |
I'm starting to think that one of the reasons that a lot of the proposals feel like they don't quite fit right is because they're actually trying to address two different problems at the same time. On the one hand, it's true that having Multiple return functions feel very, very different from single return functions, despite the seemingly small difference between the two. It's kind of like if there were extra restrictions on calling functions that take more than one argument. It feels very odd to deal with sometimes. When you call a function with multiple return values, you almost always need to do so on its own line, and it, combined with I don't know. Maybe it's just me. But I've used Go for nearly 10 years now and calling functions with multiple returns still feels kind of awkward to me sometimes. |
Thank you! |
There is one actually issue with
When you have other variables from needed after the handling creates a problem.
Or the other way variable already exists.
The |
why "should" it not exist after the if block completes? The compiler can optimize for it (if it'd deem that necessary), and there's no mental load when the err := stmt()\nif err != nil {} block completes because these almost always go together. I haven't yet looked at your proposal in depth (though kudo's for going through the effort of writing one!). However, as I also outlined in my comment above, I think more research is required into any perceived problems, before we dig into any proposals to resolve them. |
@Freeaqingme errors should not exist after the In the CopyFile example, there is There are also other oddities. If I have |
Technically in r, err := os.Open(src)
if err != nil {
return ...
}
w, err := os.Create(dst) the second instance of |
Remove use of `errors.Wrap(err, ...)` where `err` may be `nil`, replacing it with the canonical `if err != nil { ... }`. To improve code clarity, and to set precedence for not using this feature of `github.com/pkg/errors`. The `errors.Wrap(err, ...)` function call has a magical feature that if `err` is `nil` the function returns `nil`, essentially negating the need to check the error. On the surface it feels good to use because it turns this code: ```go if err != nil { return nil, errors.Wrap(err, "it broke") } return thing, nil ``` Into this code: ```go return thing, errors.Wrap(err, "it broke") ``` The shortening of the code feels good as the writer, but it can be misleading to a reader. It's very easy to misread code that uses this feature because there are not clear code paths for success and failure. Code clarity is near the top of the list of things we should value as it will help prevent us from making mistakes; when we don't understand the code we're reading, we make it do things we don't intend to. In the absence for compelling business critical reasons to write code that isn't clear, e.g. for performance critical code, we should use patterns that are easily recognizable. The `if err != nil { ... }` while tedious is one of the most well known patterns in Go code. Recent attempts to remove that pattern from Go by adding new language features were met with a lot of push back, including this very popular issue: golang/go#32825. Removing these few examples won't prevent us from using this feature, but hopefully it sets precedence. And in six months when the current version of Go (1.13) is the oldest version we support we can drop use of `github.com/pkg/errors` in favor of Go's built-in error wrapping. When we do this, we'll lose this magical feature anyway. I'd write a check for this to prevent us from writing this code, but it's too hard, and short term removing our use of the package will be better when Go 1.14 is released. There may be other places where we use this feature, as it's difficult to grep for. I mostly wanted to open this change to create a discussion point, and at least fix the cases I'd seen.
func doSomeThing() {
r, err := os.Open(filename)
panic(fmt.Errorf(err, "failed to open file: %s", filename)) //
It's panic here.
}
…On Thu, Oct 10, 2019 at 11:24 AM clearcode ***@***.***> wrote:
I think we can add a buildin function:
assert()
example:
func doSomeThing() error {
r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // in this step, just return the error
resp,err := http.Get(someURL)
assert(err, "request failed")
}
and another function that donot return an error:
func doSomeThing() {
r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // It's panic here.
}
so assert(error, args ...interface{}) is better than: if err != nil ; {
return err }
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEA2KWUI#issuecomment-540322641>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A>
.
|
The takeaway is that I want to see the actual error returned in the current
function in the current line.
…On Fri, Oct 11, 2019 at 9:55 AM Aaaa Einai ***@***.***> wrote:
func doSomeThing() {
r, err := os.Open(filename)
panic(fmt.Errorf(err, "failed to open file: %s", filename)) // It's panic here.
}
On Thu, Oct 10, 2019 at 11:24 AM clearcode ***@***.***>
wrote:
> I think we can add a buildin function:
>
> assert()
>
> example:
>
> func doSomeThing() error {
>
> r, err := os.Open(filename)
> assert(err, "failed to open file: %s", filename) // in this step, just return the error
>
> resp,err := http.Get(someURL)
> assert(err, "request failed")
>
>
> }
>
>
> and another function that donot return an error:
>
> func doSomeThing() {
> r, err := os.Open(filename)
> assert(err, "failed to open file: %s", filename) // It's panic here.
>
> }
>
>
> so assert(error, args ...interface{}) is better than: if err != nil ; {
> return err }
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <#32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEA2KWUI#issuecomment-540322641>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A>
> .
>
|
@Yanwenjiepy that's intentional, I'm big fan of Rust's |
I'm less than 10 minutes into learning Go. The very first thing I noticed in the code I was looking at was had this copy pasted over, and over, and over again:
I'm obviously not an expert, but it might be of value that it's only taken me my first glance to end up on this thread. |
That's because you're looking at code snippets for learning. Real code has to handle errors, not just panic and crash. |
True, but errors can (and often should) be grouped. That's why try/catch blocks exist in other languages. For example, the following would smell much less like dinosaurs to me:
Once again, total layman. But code is just symbols, and if they repeat enough, you can make a symbol for that too. This appears to be a very frequently repeating symbol. |
You might want to take a look at Rob Pike's Errors Are Values post to see how you can use a helper to merge errors and deal with them all at once. In practice catching all exceptions with a single clause is considered bad style in most languages that have them, because you end up hiding information about what actually happened. (And if you extend the example to break out the individual caught exceptions and not throw that information away, the code ends up as long as the Go equivalent.) |
Thanks for the link. The |
Let's say each function returns overlapping error type and you must handle all function result gracefully, how do you write tryHarder()?
It will only take someone else 1 minute to understand the below code:
|
In that example, you're totally correct. |
The Go2 proposal #32437 adds new syntax to the language to make the
if err != nil { return ... }
boilerplate less cumbersome.There are various alternative proposals: #32804 and #32811 as the original one is not universally loved.
To throw another alternative in the mix: Why not keep it as is?
I've come to like the explicit nature of the
if err != nil
construct and as such I don't understand why we need new syntax for this. Is it really that bad?The text was updated successfully, but these errors were encountered: