8 ms·
I agree, I wonder if C++/Java would have gone down the exception path if they had multiple return values? Its it seems to me exceptions are really a special kin
by voidlogic 10y ago
I agree, I wonder if C++/Java would have gone down the exception path if they had multiple return values? Its it seems to me exceptions are really a special kind of return value that doesn't clobber your single return value.
Compared to Java I have ported to Go, returning error + defer has led to much more localized (and this grokable) and less verbose error handling. Less verbose? I suppose this is not true if you throw exceptions and handle them all at the top (Go has panic for these kind of fatal events BTW), but if you regularly have try-catch-finally blocks, the Go approach is in fact less verbose.
- jjnoakes 10y agoI think I'd want more than multiple return values. I'd also want syntactic sugar to pass errors through to callers, or alternatively, ignore errors and skip future processing and have the error be the result of the future processing. These things are typically done by exceptions and monads; the "zig" language has some interesting syntax around this kind of thing as well. But just multiple return values isn't enough for me - too much "if error return error" noise for my taste.
- voidlogic 10y ago>too much "if error return error" noise for my taste. I understand that. I was pointing out this is less noise than try-catch-finally (assuming the programmer is comparing Go to C++/Java/C#). I don't deny some functional langs have more elegant handling in this respect.
- spullara 10y agoIt isn't less noisy. With exceptions I can choose to have no noise or concentrate all the noise in one part of the method. In Go there is junk all over the method deeply conflating the good path and the exceptional path.
- majewsky 10y agoIf you add exceptions, you trade noise for action-at-a-distance. From my experience, I prefer a little noise over action-at-a-distance.
- h1d 10y agoEverything is already at a distance when you load libraries and bundle up your commonly used functions elsewhere. DRY principle seems not applicable here.
- Noughmad 10y agoNo, it's not less noise, because with exceptions, you can put all you error handling in one place, or wherever is the most appropriate. With go, you are forced to handle it at literally every function call. If you want some data, "if err != nil return err" contains the four most frequently used words in Go code. See https://anvaka.github.io/common-words/#?lang=go https://anvaka.github.io/common-words/#?lang=go . For comparion, try/catch are only 34th and 35th for Java, and even lower for C# and C++.
- dragonwriter 10y ago> With go, you are forced to handle it at literally every function call. Every call that can fail, which isn't quite every function call.
- stouset 10y ago> But just multiple return values isn't enough for me - too much "if error return error" noise for my taste. This is such a big deal, I don't understand how it's defensible. Code is read dozens of times more often than it's written. When code looks like: if val1, err := thingImActuallyTryingToDo1(); err != nil { return nil, fmt.Errorf("thing failed: %v", err) } if val2, err := thingImActuallyTryingToDo2(val1); err != nil { return nil, fmt.Errorf("thing failed differntly: %v", err) } if val3, err := thingImActuallyTryingToDo3(val3); err != nil { return nil, fmt.Errorf("thing failed again: %v", err) } it's impossible to tell at a glance how the function is actually working. You have to parse out 90% garbage just to get to the details of what's really happening. Worse is that it's never this "clean" and repeatable in practice. It becomes a bunch of subtly-different variants on that theme (append if the function wasn't an error, or do something different if it errored, etc.) that the actual logic of your function is obscured in a maze of error-handling.
- majewsky 10y ago> it's impossible to tell at a glance how the function is actually working. You have to parse out 90% garbage just to get to the details of what's really happening. After a while, you just learn to ignore the `if err != nil` and get on with your life. I mean, sure, it would still be nicer to have an abbreviation, but it's not like Go code is 90% `if err != nil` (as critics always try to frame), and when I read Go code, I'm most definitely not spending most (or any) of my time stumbling over them.
- prodigal_erik 10y agoIt's not 90%, but it seems like it has to be more than 50% because not only does every call require an error check but f(g(x), h(y)) has to be broken down and given three error checks.
- stouset 10y agoYeah. In my experience it's way closer than 90% than 50%. Even with one argument, function chaining is absurdly annoying. You can't just call f(g(x)), because you have to pull out the error check and do it manually. And most of your code winds up doing error checks because if any call in the stack under a particular function produces an error, it usually winds up having to bubble the entire way up the stack.
- stouset 10y agoMultiple return values are a kludge. Combined with nil being only usable for a pointer value, it's entirely too easy to accidentally not process the error and accidentally use the non-error return value (bool, int, or other primitive type). Go should have had Rust-style enums that let you do things like Option<T> and Result<T,E>. This way, you are forbidden by the compiler from ever extracting an invalid value from a function that returned an error (or returned no value).
- curun1r 10y agoI agree wholeheartedly. Also of note, the combinators Rust offers on Option/Result make it possible to chain multiple operations that could fail and then check for the first error that occurred. In Go, you have to check each potential error immediately after it's potential return. This allows Rust to be just as clear as Go while being more concise and enforcing at compile-time that errors are handled. Propagating errors out of a function is also easier given try!/?. I really don't see a downside of Rust's approach compared to Go's after having programmed in both. But, then again, Rust's approach requires generics and I know Go's team doesn't want to go in that direction.
- iand 10y agoChaining and failing on first error is only useful for the most trivial of error situations. Non toy programs handle errors with retry strategies, alternate algorithms and fallbacks which may all be contextual.
- ovao 10y agoIt depends on the problem domain, but it's been my experience across a few domains that many errors are of the "trivial" type you've described, in that recovering from an error by taking some alternative path is usually not very valuable. A bail-out-on-any-error approach doesn't necessarily mean it's a "toy program". Nothing in Rust (or Go, even) ordinarily precludes the type of retry approach you're describing, of course.
- ngrilly 10y ago
- jcranmer 10y ago> I agree, I wonder if C++/Java would have gone down the exception path if they had multiple return values? Its it seems to me exceptions are really a special kind of return value that doesn't clobber your single return value. Because of constructors, I don't think they would have eschewed exceptions. Constructors don't return any value (in both C++ and Java ABI terms, they are implemented as void functions!) anyways, so having the "I don't know how to do what your arguments told me to do" be indicated via an exception being thrown is an intentional model rather then using return codes.
- hubert123 10y agoGo's error handling is the primary reason I'm not using that language. If I ever, even on a single line, dont handle an error, my project might fail in an unexpected, undebuggable, unlogged way. With any other language I get an exception and a stacktrace printed out to me. With go, it just silently fails. How this was ever construed to be "better" is beyond me.