4 ms·
> Explicit error handling is a choice Yeah, it is - a bad choice IMO. I know the "if err != nil" pattern is spoken up as some sort of cultural idiosyncrasy of
by xg15 2y ago
> Explicit error handling is a choice
Yeah, it is - a bad choice IMO.
I know the "if err != nil" pattern is spoken up as some sort of cultural idiosyncrasy of Go, similar to the whitespace formatting in python. But so far, I haven't seen any actual data (or even arguments) why it is superior to exceptions, or which inherent problems of exceptions it solves. (The classical example of "it makes control flow more obvious and makes it easier to ensure that mandatory finalizers are not skipped" was just disproven by this very article)
So if there is more substantial criticism against exceptions than just FUD, I'd like to know it.
- randomdata 2y agoWell, let's assume Go did commonly make use of exception handlers for error cases: defer func() { if r := recover(); r != nil { fmt.Println("Failed to write file", r) } }() f := os.Create("file") defer f.Close() io.WriteString(f, "Hello, World!") Cool. You've solved one problem layer, perhaps. But if you look closely you'll notice that code still has a bug! So clearly exception handlers aren't enough. There might be some good ideas in using exception handling, but if you strictly limit its use to errors you end up half-assing it. Why not go all the way and find a solution that works in all cases?
- matt_craig 2y agoIs it that the defer() that recovers is called before the defer() that closes (and potentially fails)?
- randomdata 2y agoNo. No rearranging of the code would fix the problem.
- matt_craig 2y agoHmm. Checked the docs, looks like os.Create returns *File and err, but the sample doesn’t check that error.
- randomdata 2y agoos.Create, per the topic of discussion, is said to throw an error using the exception handling mechanism rather than return error. The exception handler checks the error. If you focus on errors, you’re going to never get it. The whole idea here is that the problem is bigger than errors.
- xg15 2y agoMaybe I'm too dumb too see the bug here. Do you mean the issue that if both WriteString() and Close() throw an exception, the one from WriteString() will be swallowed? That used to be a problem, but has been solved in more modern implementations using suppressed exception tracking.
- randomdata 2y agoHa. I suppose you could argue that is also a bug, but not the one I was thinking of. Let's say this hypothetical Go where error over exception handling is the norm has no such issue. Think more carefully about what we're actually trying to solve. It is not just about errors.
- ghosty141 2y ago> I haven't seen any actual data (or even arguments) why it is superior to exceptions, or which inherent problems of exceptions it solves It's faster. Doing all error handling via exception is not viable if you want speed. Exceptions work well for errors in the sense that these rarely happen. But using them for general "this is the bad outcome" of an operation that can happen in the hot-path is problematic. For example pythons "next()" function which raises an exception if the end of the iterator is reached, if C++ did this it just wouldn't be used. So in my opinion exceptions are nice if you want ease of use, and explicit error handling (look at rust with their question mark operation which makes it pretty easy to do) is the way to go for performance. One advantage of explicit error handling like rust does it is: it forces you to handle the error path like other business logic. Again, if you write a short script, this is annoying and doesn't get you much but if the application is very important then doing so is a good thing since it forces you to think about where which errors can happen and how you handle them. With exceptions its very easy to completely forget that a can even happen and thus they get ignored and suddenly the program crashes with a unreadable error message.
- saurik 2y agoThis is merely an implementation tradeoff: I could trivially modify a compiler to support exceptions that were as fast as returning error sum types without any syntax changes, but it comes at the cost of making all non-failing code slower--as you have to litter the runtime with checks that exceptions elide--so we don't generally do it... but like, if you make a new language and don't go with the syntax of exceptions because you like the implementation of sum types, I feel you have misunderstood the problem space. (As for "handling" errors, you should only have a few places in the entire codebase which do that... littering the entire codebase with opportunities to feel like you might could handle an error seems like a mistake as it just encourages more handling.)
- gregjor 2y agoYou interpret Go using a different pattern for error handling as a criticism of or challenge to exceptions. Consider that languages can take different approaches, without having to get into “best” or “worse.” Every approach to error handling has advantages and shortcomings. The Go designers have explained and talked about this decision many times. Some people don’t like it and maybe choose a different language. No one pushes FUD regarding exceptions. I get the impression that if you choose a Toyota you would think that your neighbor buying a Honda poses a criticism you have to address. Discussed before many times on HN, such as: https://news.ycombinator.com/item?id=4159641 https://news.ycombinator.com/item?id=4159641 Original article: https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html https://commandcenter.blogspot.com/2012/06/less-is-exponenti...
- kaba0 2y ago> Every approach to error handling has advantages and shortcomings If under ‘every approach’ you explicitly exclude go’s terrible errno syntax sugar, and include exceptions and sum types, then yeah. There is zero advantage to go’s error handling compared to the proper sum typed solutions.
- gregjor 2y agoYou don't like how Go implements error handling. We get it. Lots of people complain about that. Use a "proper" language more to your liking. Go's designers have explained their decisions many times, I won't repeat their justifications here. Obviously they had to make choices in line with their overall vision for Go. They feel strongly that programmers should handle or at least acknowledge the possibility of every error, explicitly, right where it gets reported. Exceptions and sum types don't enforce that.
- kaba0 2y agoHow are sum types not better in literally every possible way? Like, it’s as objective as it gets. Sum types makes it mandatory to unwrap the result - ergo some form of error handling. It is not the case with go.
- kevindamm 2y agoSibling comments have good points but what made it click for me was the book "Exceptional C++" where all the possible sources of exceptions are pointed out -- it's usually twice as many as you would first guess by looking at the code, sometimes made more complicated by type-converting constructors and operator overloading and RAII. Then consider that if any code you're calling is not exception-safe it makes the task of writing your code to be exception safe that much harder. Then add on top of that the question of responsibility -- do you handle the exception close to where it gets thrown or farther up the call stack? Handling it too close to the issue may be missing some context and result in a lot of the same exception handling across many areas of the codebase. But handling it too far up misses context, too, and often leaves the program in an uncertain state where it's not clear whether the show must go on or if it's time to shut it down. It's not uncommon to see a mix of exceptions and returned error values to try and find a happy middle ground there. Java tried to paper over some of these problems with fewer gotchas (more explicit separation between resource allocation and initialization) and compiler checks that exceptions are in the method signature and are always handled _somewhere_ up the call stack but this often results in handling of very vague exception types at a high level. Some developers, uncertain what the right way to handle an exception is, and not wanting to crash the program, will just silently drop the exception instead! Admittedly, you can do this in go-style and C-style error handling, too, but at least then it's not as far removed from the source of an error because it's annoying to have to keep passing an error response through so many function signatures. I used to use exceptions a lot in C++ and Java. This changed when I started working at a place where a lot of C++ is used but without exceptions. It was ostensibly about runtime costs but when seniors were pressed on the issue it was clear there were a lot of these other reasons stated above, and ultimately about readability of the code (error handling being close to error source) and a philosophy of failing quickly when assertions fail (because keeping calm and carrying on leads to programs running that should have died before they could make more of a mess). I know it's unpopular but, in light of some real problems with exceptions, I actually prefer the way Go makes you do it. It often encourages doing all the setup in one (or a few) function scopes, and it becomes pretty evident from a function's signature whether you should expect something to go wrong or not. Would I prefer it for a game-dev scripting language? no! But for a system language it is better IMHO
- devjab 2y agoI prefer it because of location. The error handling happens where the error occur. It makes large systems much more maintainable in an enterprise setting. It’s not like exception handling and throwing around things to have them caught later is inherently bad. It’s just a different philosophy, one that I don’t personally like anymore. It’s down the same alley as things like OOP, SOLID or DRY. Things which have good concepts that way too often leads to code bases which are incredibly annoying to work with. Maybe not for small systems with short life times, but for systems where you’re going to be the 100th person working on something that’s been running for 30 years it’s just nice to not have to play detective. I’d like to put a little disclaimer in here, because that isn’t inherently a consequence of exception handling or any of the other concepts but it’s just what happens when people work on code on a Thursday afternoon after a tough week. The simpler less abstract things are made, the easier it’ll be to unravel, and simple error handling is dealing with the errors exactly where they occur. As others point out, it’s not without its disadvantages. It’s just that in my experience, those are better disadvantages than the disadvantages of implicit error handling.
- gwd 2y ago> But so far, I haven't seen any actual data (or even arguments) OK, here's an argument. - In order to write resilient software, programs must handle not only the "happy path" when things succeed, but the path where things might fail. - Thus it is important for developers to 1) be aware of which operations may fail fail, and b) think about what the program should do in that case. - Exceptions make it easier for the programmer to forget that something might fail, and to avoid thinking about what to do if it does fail. - Go's error handling idiom makes it clear that an operation might fail, and prompts programmers to think about what to do in that case. (They may of course choose not to think about it, but at least they made a conscious choice at some level.) Thus Go's error handling idiom nudges developers towards more resilient software than exception-based workflows. Or to put it differently: Programming systems which may fail simply is ugly: there are an exponential number of ways a system may fail, and each one must be handled correctly. Exceptions hide this ugliness, but by doing so make it more likely that there will be cases not handled correctly. By exposing this ugliness, Go makes it more likely that most cases will be handled correctly.
- kaba0 2y ago> In order to write resilient software, programs must handle not only the "happy path" when things succeed, but the path where things might fail And exceptions let you handle error conditions without making the actual business logic harder to read, with as little or much specificity as required. > Thus it is important for developers to 1) be aware of which operations may fail fail, and b) think about what the program should do in that case Checked exceptions/effect types exist, being explicit or implicit in function signatures is not a fundamental property of exceptions. And what is clearer in terms of error handling — if err being every third line, with questionable handling logic, e.g. just printing or swallowing stuff (or gestures at the article), and definite human error from repetition —— or a well-defined block with proper scoping, without which the error case does the only reasonable thing — automatically bubbles up, making it possible to handle higher up. There is often no immediate action that can be done in certain exceptional situations, e.g. your ordinary function that writes a file can’t do anything about a full disc. The best it can do is to yell, so that the action that called it somewhere can do some evasive action, e.g. re-trying/notifying the user/etc. > Exceptions make it easier for the programmer to forget that something might fail, and to avoid thinking about what to do if it does fail. Disagree. If anything, something not being in a try-catch block says that it will be handled higher up (or checked exceptions making it part of the signature), and when it’s surrounded by it, I know what is the happy path, and unhappy path immediately, without it being crossed over (usually badly), as it would happen with if errs. > Go's error handling idiom makes it clear that an operation might fail What about the case when it both returns a value and an error? > and prompts programmers to think about what to do in that case Blindly if erring and printing out a random string is not error handling. That’s just noise, and a terrible trap for yourself, having to grep for useless error codes later on.