6 ms·
I think they use too many sentinel errors [0] I have been doing Java for two decades, and I thought you need to handle individual errors by type. Using Go, I've
by KingOfCoders 2y ago
I think they use too many sentinel errors [0] I have been doing Java for two decades, and I thought you need to handle individual errors by type. Using Go, I've learned from the code I write, 90%+ of errors I don't need to handle individually, or I can't do anything except bubble an error up. There is the rare case (10%) when a file does not exist, and I try to read an alternative one and I don't bubble up an error.
For customer support I also found it much easier, instead of an error number, print a UUID that customers can give to support, and that UUID (Request ID) then can be found in the logs to find out what happened by developers.
[0]:https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully https://dave.cheney.net/2016/04/27/dont-just-check-errors-ha...
- RVuRnvbM2e 2y agoThis was exactly my train of thought. I even went looking for Dave's blog post about it before I saw your comment. :D
- deleted 2y ago[deleted]
- gf000 2y ago> Using Go, I've learned from the code I write, 90%+ of errors I don't need to handle individually, or I can't do anything except bubble an error up So... exceptions are better, because they would do the correct thing by default in the majority of cases?
- lordofgibbons 2y agoUnless you forget to catch the right type of exception. Then all hell breaks loose.
- deleted 2y ago[deleted]
- bigstrat2003 2y agoPeople bitch about checked exceptions in Java but this is precisely why I think they're a great idea. You can't forget to catch the right type of exception.
- lomnakkus 2y agoNo, but you can easily end up missing some because somebody wrapped them in some sub-type of RuntimeException because they were forced(!) to. This happens all the time because the variance on throws clauses it at odds with the variance of method signatures (well, implementations, really -- see below). A new implementation of a ThingDoer usually needs to do something more/different from a StandardThingDoer... and so may need to throw more types of exceptions. So you end up having to wrap exceptions ... but now they don't get caught by, say, catch(IOException exc). If you're lucky you own the ThingDoer interface, but now you have a different problem: It's only JDBCThingDoer which can throw SQLException, so why does code which only uses a StandardThingDoer (via the ThingDoer interface) need to concern itself with SQLException? Checked exceptions in Java are worse than useless -- they actively make things worse than if there were only unchecked exceptions. (Because they sometimes force the unavoidable wrapping -- which every place where exceptions are caught needs to deal with somehow... which no help from the standard "catch" syntax.)
- iainmerrick 2y agoOne thing you can do in Java is parameterise your interface on the exception type. That way, if the implementation finds it needs to handle some random exception, you can expose that through the interface -- e.g. "class JDBCThingDoer implements ThingDoer<SQLException>". Helper classes and functions can work with the generic type, e.g. "<E> ThingDoer<E> thingDoerLoggingWrapper(ThingDoer<E> impl)". I think this works really well to keep a codebase with checked exceptions tractable. I've always been surprised that I never saw it used very often. Anyone have any experience using that style? I guess it's not very relevant any more because checked exceptions are sadly out of fashion everywhere. I haven't done any serious Java for a while so I'm not on top of current trends there.
- 2y ago
- Yoric 2y agoIs it time to brag about Rust error-handling or should we wait a little?
- gf000 2y agoIt's certainly better than Go's (Go's is barely better than C's and that's quite a low bar), but I don't think that sum types are the global optimum. Exceptions are arguably better from certain aspects, e.g. defaulting to bubbling up, covering as small or wide range as needed (via try-catch blocks), and auto-unwrapping without plus syntax. So when languages with proper effect types come into mainstream we might reach a higher optimum.
- LinXitoW 2y agoMaybe I'm too pessimistic, but Rust style error handling feels like the global optimum under the constraint that the average developer understand it. Go is a language that exists purely because people saw Monads in the horizon and, in their panic, went back to monke, programming wise. Rust error handling is something that even many Go fans have said is a good abstraction.
- Yoric 2y agoNo, sum types are certainly not a global optimum. But they remain the best error-handling mechanism that I've used professionally so far. Effect types (and effect handlers) are very nice, but they come with their own complexities. We'll see if some mainstream language manages to make them popular.
- KingOfCoders 2y agoAfter a decade of Scala and Rust I no longer believe in monads and prefer the way Go does error handling. for a <- a() b <- b() { return a + b } looks nice but only by hiding error handling. Today I like looking at code and see the error handling. With monads you end up with monad stacks and transformers which introduce their own failure states.
- 2y ago
- xienze 2y agoYou may be thinking a bit too much about what happens in _Go_ when you forget to check for an error response from a function -- the current function continues on with (probably) incorrect/nil values being fed to subsequent code. In Java when an uncaught exception is thrown, the exception makes its way back up the call stack until it's finally caught, meaning subsequent code is _not_ executed. It's actually a very orderly termination. In any Java web framework (Spring et al) there's always a centralized point at which exceptions are caught and either built-in or user-specified code is used to translate the error to an HTTP response. This makes for much more pleasant code that is mostly only concerned with the happy path, e.g., my REST endpoint doesn't have to care if an exception is thrown from the DAO layer as the REST endpoint will simply terminate right then and there and the framework will map the exception to a 500 error. Why anyone would prefer Go's `if err != nil {}` error handling that must be added All. Over. The. Place. at every single level of the application is beyond me.
- LinXitoW 2y agoMy slightly snarky take is that liking Go is simply a defensive reaction to one too many AbstractFactoryBeanFactory. Too many abstractions overloaded their "abstraction-insulin", so now they can only handle minute amounts of abstraction.
- gf000 2y agoI liked your other comment's take with the monads better :P
- LinXitoW 2y agoWhich Go doesn't fix either, because their errors are all just "error", aka you can also forget to catch the right type of error. If only there was a way to combine optimizing the default path (bubbling), and still provide information on what errors exactly could happen. Something like a "?" operator and a Result monad...
- ceving 2y agoExceptions are easier for the programmer. The programmer has to write less and they clutter the code less. But exceptions require stack traces. An exception without a stack trace is useless. The problem with stack traces is: they are hard to read for non-programmers. On the other side Go's errors are more work for the programmer and they clutter the code. But if you consequently wrap errors in Go, you do not need stack traces any more. And the advantage of wrapped errors with descriptive error messages is: they are much easier to read for non-programmers. If you want to please the dev-team: use exceptions and stack traces. If you want to please the op-team: use wrapped errors with descriptive messages.
- prirun 2y agoI tend to use "catch and re-raise with context" in Python so that unexpected errors can be wrapped with a context message for debugging and for users, then passed to higher levels to generate a stack trace with context. For situations where an unexpected error is retried, eg, accessing some network service, unexpected errors have a compressed stack trace string included with the context error message. The compressed stack trace has the program commit id, Python source file names (not pathnames) and line numbers strung together, and a context error message, like: [#3271 a 25 b 75 c 14] Error accessing server xyz; http status 525 Then the user gets an idea of what went wrong, doesn't get overwhelmed with a lot of irrelevant (to them) debugging info, and if the error is reported, it's easy to tell what version of the program is running and exactly where and usually why the error occurred. One of the big reasons I haven't switched from Python to Go for HashBackup (I'm the author) is that while I'd love to have a code speed-up, I can't stomach the work involved to add 'if err return err("blah")' after most lines of existing code. It would hugely (IMO) bloat the existing codebase.
- ndriscoll 2y agoMessages and stack traces in the error are orthogonal to errors-as-values vs. exceptions for control flow. You could have `throw Exception("error fooing the bar", ctx)`. You could also `return error("error fooing the bar", ctx, stacktrace())`. Stack traces are also occasionally useful but not really necessary most of the time IME. Go's error handling is annoying because it requires boilerplate to make structured errors and gives you string formatting as the default path for easy-to-create error values. And the whole using a product instead of a sum thing of course. And no good story for exception-like behavior across goroutines. And you still need to deal with panics anyway for things like nil pointers or invalid array offsets.
- bccdee 2y agoWhen there's an exceptional case, it's better to handle that explicitly. I think Rust does that best with its single-character ? operator, but I don't want exceptions invisibly breaking out of control flow unless I give them permission to. `if err != nil` is a fair enough way of doing that.
- int_19h 2y agoIt's not a good way to do this because it doesn't force you to either handle or propagate.
- bccdee 2y agoYeah there are linters that force you not to implicitly discard errors, but that should really be a compiler error. Still, that's not a problem inherent to the Go's error-handling model.
- emseetech 2y agoBetter is subjective, but I prefer errors as return values because then the function signature states whether an error has to be handled or not. Exceptions can be forgotten about, but returned errors have to be explicitly ignored.
- KingOfCoders 2y agoExceptions solve the 10% better with the tradeoff of their inflexibility for the other 90% of cases.