6 ms·
> `if err != nil {}` What would you like instead?
by MisterTea 2y ago
> `if err != nil {}`
What would you like instead?
- Dylan16807 2y agoThe most basic thing would be a compact way to say "do this call, if there is an error return it, otherwise give me the value"
- 9rx 2y agoThat would be the most basic thing, but you'd never be able to use it (toy code written for an internet comment aside). 1. Errors quickly lose their usefulness if you simply pass them up the stack. Even just on the surface, if you don't even know where the error came from, good luck making sense of it. Other languages have tried to avoid that problem by having things like "sidecar" handlers that can handle the error elsewhere, but in the end that's just moving code around. You haven't actually solved the overhead of needing to do something with the error. 2. Errors simply passed up the stack are, more often than not, going to leak implementation details. Consider a function that fetches data from a SQL database, where the underlying operations produce a "SQLNoResults" error. If you let that flow through, callers are going to start to rely on it. Now, imagine new requirements dictate that you need to fetch the data from an HTTP service instead. If you continue to simply pass the error along, now callers are going to get a "HTTPNotFound" error instead, breaking their usage. Not a good situation.
- Dylan16807 2y ago1. It's not just moving things around. Often you want a segment of code to all do the same thing on error. 2. There are places where you want to abstract your errors, but those places are not every function call or even most function calls.
- 9rx 2y ago> Often you want a segment of code to all do the same thing on error. Do you mean often you want multiple segments of code to all do the same thing on error? If there is only one segment then you well and truly have just moved things around. Multiple segments all doing the same thing on error would give more justification to centralizing functionality, but at the same time if you have multiple segments of code all doing the same thing you've probably not thought your design through. Papering over design mistakes with language features is commonly done, but I'm not sure it is something to strive for. > but those places are not every function call or even most function calls. If the original error is your own you don't need to abstract it, but if you are passing your own errors through multiple levels of indirection you've, again, probably not thought your design through very well. Papering over design mistakes with language features is commonly done, but I'm not sure it is something to strive for.
- Dylan16807 2y ago> Do you mean often you want multiple segments of code to all do the same thing on error? If there is only one segment then you well and truly have just moved things around. If a segment has 6 function calls and you want the same error handling for each one, you can't get rid of the boilerplate with the current language. > If the original error is your own Assume the original error is not my own then.
- 9rx 2y ago> If a segment has 6 function calls and you want the same error handling for each one, you can't get rid of the boilerplate with the current language. My deepest sympathies for the person who has to respond to the resultant error once you've collected your paycheque and have moved on to the next project. Which of the six functions produced the error? Nobody knows. That may be all well and good for a contrived internet comment example, but if you write real code like that someone's life is soon going to become a living hell. Every other language has recognized that you can't have the same error handling for each of the function calls. Even where they have special error handling semantics have special ways to ensure that the handling is different in each case. Why do you think it would work in Go? > Assume the original error is not my own then. Then you've forever hitched your horse to their code. A better or more performant replacement comes along in the future and you want to use it instead? Too bad. You can't without breaking your own API – and for what reason? But, okay, we accept that you like to live life on the edge (or come from the Javascript world and thus don't know any better) and if the people using your functions start having breakage, too bad so sad. However, if you're just passing values through from another package, why are you really offering your callers in the first place? Why don't they just use the other package directly?
- Dylan16807 2y agoI like how the first half of your reply assumes the difference between errors disappears, and the second half of your reply assumes the difference doesn't disappear. Just because I share handling doesn't mean I would throw out all the information, and if I have a filesystem error while setting up a file I'm going to do the same cancellation paths no matter what particular filesystem error it is. > Then you've forever hitched your horse to their code. A better or more performant replacement comes along in the future and you want to use it instead? Too bad. You can't without breaking your own API – and for what reason? No, I did not say that. What I said is that the place to prevent that is not every function call. If my code goes 4 functions deep, I need at least one of them to handle errors I didn't cause, or convert them into my own errors for the sake of a stable API. But many of the other functions can pass errors through.
- valenterry 2y ago> Errors quickly lose their usefulness if you simply pass them up the stack. Nope, not in languages where you have a stacktrace attached. > Errors simply passed up the stack are, more often than not, going to leak implementation details. That's why in a good language you can just wrap them at the right level of abstraction.
- deleted 2y ago[deleted]
- 9rx 2y ago> Nope, not in languages where you have a stacktrace attached. Like you say, the stack trace needs to be attached. For that you, at very least, need a "sidecar" handler if not done so in the same execution path, as we discussed earlier. Did you, uh, forget to read the thread? > That's why in a good language you can just wrap them at the right level of abstraction. You can move the logic around, but you can't avoid it, as we discussed earlier. Did you, uh, forget to read the thread?
- valenterry 2y ago> Like you say, the stack trace needs to be attached. For that you, at very least, need a "sidecar" handler if not done so in the same execution path, as we discussed earlier. Did you, uh, forget to read the thread? I just read it again, but I'm not sure what you mean. And sorry, I'm not familiar with that terminology (sidecar) but from the perspective of the user/developer, does it matter? As system or library developer, I don't need to do anything - I'll have the stacktrace available when I need it. There is extra code necessary. (one has to be mindful of the performance, but that's it) > You can move the logic around, but you can't avoid it, as we discussed earlier. Did you, uh, forget to read the thread? Doesn't have to do anything with moving logic around. Let's say you are function foo and you call other functions and one of them is bar and it will fail with barError. Then, to avoid breaking your (= foo's) consumers if bar changes its internals, you simple wrap bar's error with your own. That can be as simple as doing `bar.mapError(barError -> fooError(cause = barError))` and that's it. That is all I wanted to say. Then, depending on the language, you stil don't have to repeatedly do "`if err != nil {}`" or so. There are enough alternatives, e.g. monadic error handling like in Haskell or macros like in Rust.
- elvlysh 2y ago> Do this call value, err := function() > if there is an error return it if err != nil { return err } > otherwise give me the value // rest of the code goes here
- Dylan16807 2y agoThat's not compact.
- elvlysh 2y agoCompactness is in the eye of your beholder. You might have the eyes of a spider, but I have the eyes of a large orc.
- Dylan16807 2y ago> Compactness is in the eye of your beholder. Not really. If I want to do foo().bar().baz() it expands to six lines.
- int_19h 2y agoAnd then you forget to write the err check once out of 100 times you have to write this verbiage. And the compiler lets you, because err was already checked in that same function (but for a different call), so it's not unused anymore.
- snuxoll 2y agoYeah, not a huge fan of error handling in go - stuck relying on a linter to catch you and because of shadowing rules it's extremely difficult to make it look nice. Rust's `?` operator on Result<T,E> types is flipping fantastic, puts all of the following to shame. // can forget to check err thing, err := getThing() if err != nil { panic(err) } // More verbose, now you could possible forget to assign thing var thing Thing if t, err := getThing(); err != nil { panic(err) } else { thing = t } // What I end up doing half the time when I've got a string of many // calls that may return err as a result of this var whatIActuallyWant string if first, err := getFirst(); err != nil { return err } else if second, err := doWith(first); err != nil { return err } else if final, err := doFinally(second); err != nil { return err } else { whatIActuallyWant = final } It's actually to the point that in quite a few projects I've worked on I've added this: func [T] must(value T, err error) T { if err != nil { panic(err) } else { return value } }
- dharmab 2y agoI would have preferred a Result type.
- elvlysh 2y agoSo you would prefer to write match result => error instead of if err != nil? Excuse me for not being convinced that this isn't just fussing over syntax.
- valenterry 2y agoIf a language supports Result types, that usually means it also comes with some kind of syntax to use it. Check out some Rust code (or Haskell, Scala, F#, ...) and you will find that in those languages you barely match on the result because you don't have to. Whereas you have to do "if err != nil" in golang all the time.
- deleted 2y ago[deleted]
- stouset 2y agoIt isn’t just fussing over syntax, but that would require you actually being curious enough to learn why instead of needing to dismiss people’s valid critiques of out hand.
- tubthumper8 2y agoIt's not just syntax, a sum type forces checking for the error whereas a product type does not, so it's a semantic distinction too
- pas 2y agoBash script (without -e) continue merrily even if the last command's exit status is non-null. PHP also ignored errors out of the box (even has @ to suppress error output), but error_reporting(-1); is basically how every sane framework starts, and PHP 8 set the default level to E_ALL [1](in 5.3 accessing undefined variable was also ignored (notices - as far as I remember)[2]) Python and NodeJS by default aborts on error. (Python had exceptions before user-defined classes in the last millennia. [3]) ... Rob Pike said in 2015 "don't just check errors, handle them gracefully." [4] I think Scala/ZIO has the most powerful and comprehensive and compact [5] error handling machinery that I have experience with, that's where it's the easiest to live up to Mr Pike's admonition. ... All are fine. I hope/predict Go will pull a Bash or PHP and will introduce flags to turn some/most unchecked errors into aborts. (Or perhaps people will build tooling and alternative stdlib eventually.) Result types are nice because it allows library developers to encode a shitton of really useful semantic information into them, and it's really easy to handle them however the downstream user wishes. (Rust offers .unwrap(), TypeScript ! (assume non-null), and JS itself has the ? optional chaining operator.) [1] https://php.watch/versions/8.0/error-display-E_ALL https://php.watch/versions/8.0/error-display-E_ALL [2] https://stackoverflow.com/a/69291454/44166 https://stackoverflow.com/a/69291454/44166 [3] https://python-history.blogspot.com/2009/03/how-exceptions-came-to-be-classes.html https://python-history.blogspot.com/2009/03/how-exceptions-c... [4] https://softwareengineering.stackexchange.com/a/406347 https://softwareengineering.stackexchange.com/a/406347 [5] https://zio.dev/overview/handling-errors/ https://zio.dev/overview/handling-errors/