5 ms·
I don’t buy the “huge pain” argument. I write lots of Python and Go, and the error boilerplate is a non-issue. I also appreciate that it’s explicit instead of i
by weberc2 7y ago
I don’t buy the “huge pain” argument. I write lots of Python and Go, and the error boilerplate is a non-issue. I also appreciate that it’s explicit instead of implicit.
- malisper 7y agoIt's not just a pain to write. I've accidentally introduced way more bugs through Go style error handling than through Python style error handling. Some examples: Forgetting that a function returns an error: ... foo() // foo returns an error that isn't being handled. ... Forgetting to check the error returned by a function. Note a linter won't pick this up since the err variable is used later. ... err := foo() err = bar() // The previous error will go unhandled. ... Accidentally typing return nil instead of return err: ... if err != nil { return nil } ... And in the case of the errors library, there's times where I will call a builtin function that returns an error and forget to call errors.WithStack. Every once in a while I'll come across an error without a stack trace and I'll have to hunt down where it came from: ... err := json.Unmarshal(bytes, &obj) if err != nil { return err // should be errors.WithStack(err) } ... All of these issues look just like normal bug free Go code. On the basis that I've introduced more bugs this way, I prefer Python style error handling by far.
- sagichmal 7y ago> All of these issues look just like normal bug free Go code. Not to me.
- alphachloride 7y agook
- malisper 7y agoI don't know. They're hard for me to find. How would you pick up on the first and last example? They are possibly correct depending on the exact functions you are calling. The other two examples, I could maybe understand, but they still look pretty close to normal Go code. In the case where you forget to handle an error, you need to be able to recognize the absence of the error checking bit. It would be one thing if there was extra code that looked wrong, but looking for the absence of code makes it hard to spot. In the case where you return nil, it looks exactly like a normal early-exit from a function. You need to be able to recognize that the code is not a normal early-exit, and that the three letters "err" were swapped for the three letters "nil".
- rubyn00bie 7y agoHaving written maybe two lines of Go, many years ago, I guess I'm surprised that all of these cases make it past the type system. Like shouldn't it bitch if you try to return nil when it's expecting a non-nil value or an error? I guess I'll go try to find an online go thinger to find out.
- vertex-four 7y agoThere’s no way to tell Go that a value shouldn’t be nil.
- rubyn00bie 7y agoThanks for the info! And... Woah. That's kind of horrifying to me (though I totally get the explicitness of it)... does this just mean liberal amounts of: thing != nil everywhere? Or is the rest of the memory management I guess, uhh.., good/magical enough you don't have to worry about it constantly in calls further down the stack if you've checked it once? Or are you always feeding the nil check beast?
- malisper 7y ago> does this just mean liberal amounts of: thing != nil everywhere Yes, it does. Whenever you call a function that can possibly fail, you are supposed to add: if err != nil { return err } You can think of it as unwinding the stack by hand. That's why a lot of people complain about Go error handling so much. That and a lack of generics. Looking at some code I've written, about 15-20% of the lines in a file are responsible for error handling. > Or is the rest of the memory management I guess, uhh.., good/magical enough you don't have to worry about it constantly in calls further down the stack if you've checked it once? I don't quite understand the question here. Are you asking about a performance impact of having nil checks everywhere? If I had to guess, I would think there's a negligible performance impact because the branch predictor will always predict the happy case. As for memory concerns, as long as you are in the happy case and returning nil, no memory needs to be allocated. It's the same reason null doesn't require any memory allocation in other languages.
- weberc2 7y agoI don’t know man. I have scarcely seen bugs like these and most of them seem pretty conspicuous to me (maybe I’ve just been writing Go long enough), but I see loooads of uncaught Python (and Java) exceptions internally and from high-profile third party tools.
- malisper 7y agoThat's very possible. It's very easy to forget try...catch and wind up with uncaught exceptions. Honestly though, I would prefer the uncaught exception case. When an uncaught exception is thrown, it's very clear you have an uncaught exception and you know exactly where it came from. In the examples I wrote, you will accidentally catch an error silently. You will never know if anything went wrong unless silently catching the error triggers an issue somewhere else. Even then, it's pretty hard to trace back the bug to the lack of error handling code somewhere else.
- Thaxll 7y agoTry golangci-lint it catches all your use case.
- malisper 7y agoThanks! It looks like by default it catches the first two. Is there a way to configure it to catch the other two? I don't think it can catch the case where you accidentally return nil because sometimes you do actually want to return nil when you see an error. I also couldn't find any linter that checks that you are using errors.WithStack when needed.
- weberc2 7y agoLinting for WithStack seems like it might be tough since you want to make sure the error was annotated exactly once (I think that’s the intended use, anyway?). The linter would need to know whether or not a fallible function call has annotated the error or not. Seems like an interesting exercise in any case.
- deleted 7y ago[deleted]