9 ms·
Go error handling
- martinml 12y agoOn GitHub: https://github.com/search?q=%22if+err+%21%3D+nil%22+extension%3Ago https://github.com/search?q=%22if+err+%21%3D+nil%22+extensio...
- JensRantil 12y agoI don't see what's interesting with this?
- Artemis2 12y agoYeah, I don't get the point of this; that's how it's done in Go. You might want to search "catch (Exception e)" for other languages.
- royjacobs 12y agoExcept in Go you would be manually "rethrowing" exceptions whereas in other languages that is done automatically.
- leaveyou 12y agoIt's verbose and explicit but this is like using tests for your programs: boring and annoying short term but very useful medium and long term.
- royjacobs 12y agoYes, that can definitely be the case. It's just a hard problem in general. So far the explicitness of Go's error handling reminds me a little bit of checked exceptions in Java: I appreciate the idea but I'm not sure it's the best thing we could do.
- jerven 12y agoVerbose and explicit is what made java checked exceptions the sign of the demon for some programmers. The err pattern in GO might end up the same. In the end I think the problem is that there are two separate classes of programs. The first is where crash on exception is tolerable and even a desired property (or blindly and optimistically continuing). The second is always deal with exceptions and any error no matter how rare and unlikely they are, and attempt to recover and deal with them properly. No one language and standard library can really please both sides.
- norswap 12y agoI think it shows that while the Go people usually say that their error handling is better than exception, most often they either exit on exception (which is like not catching an exception) or pass the error up the call stack (the default behaviour for an uncaught exception). This means that this way of doing error handling is actually just verbose. Which is also my opinion.
- sagichmal 12y ago> most often they either exit on exception (which is like > not catching an exception) Not really. Exiting on error is an explicit decision that you make, hopefully only in your top-level (func main) scoping block. Letting an exception bubble up to that level without catching it is invisible. > or pass the error up the call stack (the default > behaviour for an uncaught exception) The crucial difference, which the Go team takes great pains to elucidate and fans of exceptions never seem to acknowledge, is that by treating the error as a first-order value, you make its propagation up the call stack explicit, in the normal i.e. visible code path. That makes reasoning about your error path a lot easier, and makes producing code that handles errors correctly a lot simpler. The "Eureka" of error handling in Go isn't a massive thing. It's just the reification of the small and important understanding that errors are values, just like anything else.
- norswap 12y agoWell, Java tried to make it explicit with checked exceptions, and everyone hated that. And checked exception are a much better design than the way Go does it: either you catch, which is like checking the error value in Go, or you don't and annotate your function signature with "throws MyException", which is more explicit and terser. The thing that some Go fans miss is that it's actually pretty rare (in my own experience, but I've seen a lot of people echo the same thing) to have to deal with an exception. Usually an exception means that the show is over -- at best you'll report it to the user and continue running your main loop if your application is interactive. Or you may retry the operation a certain number of times. But most of the time you just want to stop the application, or let some upper level deal with it, and those are exactly the default behaviors of exceptions.
- smcl 12y agoMaybe it would be better use the title of the submission to draw attention to exactly what is interesting here. All I can see is "lots of people use 'err' as the name of their returned error value in Go code on ohloh"
- barrkel 12y agoMost of the time, the only things done with an error are fatal exit or panic - the same code, over and over and over again. It looks much worse than exceptions. (Except C++ and Java exceptions, as both are exception anti-patterns in different ways.)
- NateDad 12y agoI don't know what codebases this covers, but GOOD Go code does not panic.
- sigzero 12y agoGOOD <insert your language here> code does not panic. Except people write code...
- dllthomas 12y agoBut this seems more like "good Haskell code does not use unsafePerformIO" than "good C code does not segfault" - if you can find violations with a grep, it's much less of a concern to rely on programmers to get it right and not use it where inappropriate (which is not to say it's not a concern at all).
- barrkel 12y agoWhen something has an error, you have approximately four choices: 1) fix it locally 2) terminate 3) ignore 4) delegate decision to caller (1) is rarely an option because failure conditions are usually beyond the control of the low-level code performing the operation. That's because error conditions usually occur at the edges of an application, typically I/O of some sort, and typically are the implementation of a higher-level semantic action. If you're trying to open a file or connect to an address, what can you reasonably do if the file is missing or the network is unavailable? (2) is not an option for systems beyond a trivial size, unless the system is composed out of little executables. There's an argument for going this route in an actor system, but most Go apps are not actor systems. (3) is usually only an option for throwaway programs - they're probably better off terminating, in case they do something bad after preconditions have been violated. Which leaves (4) - delegation to the caller. This is the most composable option, since it lets the composer of the components control the error strategy. In its most flexible form, this looks something like Lisp conditions; it's easy enough to emulate with a stack of error handler callbacks, but the benefits only come when the handling system is consistently used everywhere. The more common version is to unwinds the stack until someone is able to deal with the problem. Whether you unwind explicitly with checks to an 'err' return code, or implicitly with panic, or with longjmp, or with exceptions in a language with that feature, doesn't really matter. You're still doing the same thing, semantically and with code flow. Which error handling strategy is more error prone? The most manual one - the one that makes it easiest to drop the error on the floor, or otherwise molest it on its path from cause to handler - would be my bet. Java's pathology here is statically checked exceptions; this encourages molestation of the error on its path, since the error value potentially needs to be of a different type at different points in the code flow. This risks obscuring the cause of the error, since errors are a function of implementation - but Java encourages expressing errors in terms of interface. C++'s pathology is that it allows code flow through user provided code that is ill suited to throwing exceptions. The added complexity spawns whole books worth of material on how to write C++ code in an exception safe manner. Good exception handling style in languages with sane exceptions normally means almost never catching exceptions except for high level orchestration routines; reacting to unwinding by cleaning up resources; and generally taking a transactional approach to operations. I believe that good error handling with explicit error codes doesn't do anything semantically different in response to an error - it just does it all manually, i.e. in an error-prone fashion. And it's my biggest problem with Go.
- Intermernet 12y agoIs this meant to be a negative comment on Go error handling not conforming to a single pattern, or a positive comment on Go error handling not needing to conform to a single pattern? Or is it just a random code search posted on HN?
- danielheath 12y agoIt's natural that most public code panics on error, even though it's bad practice. Error return values (e.g. go) are better for building large systems where stability & predictability are important (that is, cost to maintain). Exceptions are better for quickly building systems (that is, cost to build).
- threeseed 12y agoYou have that completely backwards. Exceptions are much better for large systems. You can define standardised behaviour regardless of where an error occurs. You can let unexpected errors 'bubble up' and be managed in a way that preserves the integrity of the system. You have the flexibility to recover from errors and let errors be silenced. There is unparalleled flexibility with exceptions. Go's approach is frankly arcane.
- MetaCosm 12y agoYou are 100% right on unparalleled flexibility. Why limit your self to one set of flow control operators when you can have two! Also, lets make one break out of another one at any point of exception! The flexibility is endless! You are 100% right, it is terrifyingly flexible. There is a reason exceptions as flow control is considered an anti-pattern.
- threeseed 12y agoSo what you're saying is that the fundamental core of Java, Scala, Clojure are anti-patterns ? That's a strong statement for a platform that almost every enterprise application uses. Go's error handling approach is also flow control. You receive an error and have to go and do something different based on that error. With Go it is explicit. With say Java you have the choice of being explicit or implicit. Flexibility is vital when you are working on large applications where the business logic varies wildly throughout the system.
- dscrd 12y ago>So what you're saying is that the fundamental core of Java, Scala, Clojure are anti-patterns ? That's a strong statement for a platform that almost every enterprise application uses. Just so. You think something being used in enterprise is a worthy argument?
- raphael_kimmig 12y agoThis is really interesting to me because it shows what to think of the claim that explicit error handling makes go code more reliable than languages which use exceptions. When using go I often get the feeling of doing something wrong. Because I know I'm supposed to "handle errors" but all I really want is the program to blow up and hand me a stack trace. This gives me the worst of both worlds, I have to manually trigger something that approaches the unhandled exception behavior of other languages while retaining the verbosity of explicit errors returns. Now it seems like I'm not the only person struggling with this. Has anyone come up with a sane approach to that problem?
- NateDad 12y agoFor tiny programs you use yourself "blowing up" is fine. For code in production, it's probably not. Thinking about what can go wrong and writing code to handle it ahead of time is a large part of software engineering. If the network times out trying to download that file, what do you do? Do you blow up? Or do you retry a few times first? Do you pass the error back and let the caller do the retry? This is the stuff Go makes you think about. This is what makes your programs more robust. Things break in the real world, good code handles that gracefully. Blowing up is not graceful.
- MetaCosm 12y agoI wouldn't say "more reliable" -- I would say "easier to reason about". Errors are just another returned variable, nothing more, nothing less. Go doesn't encourage tracking two independent flow control sets -- one for exception flow, and one for normal program flow. I have seen all sorts of complex nastiness created via the multiple flow control sets in Java. I write server code the runs for long periods of time, the amount of times I want to blow up and get a stack trace is exceptional low, only when continuing to run is more harmful than having the service completely down. As for just wanting it to blow up -- just ignore the error handling, it will blow up. Instead of putting the error in a variable, just throw it away using _ ... and the next dependent thing will blow up. Not very elegant, but it will remove all the boilerplate you don't like, and it will crash hard like you do like... alternatively write a crashOnError(err error) function and call that passing in error -- and all it has to do is if (err != nil) { panic(err) }
- orangeduck 12y agoSo I get it, and it makes a good point, but this could be more of a symptom than a disease. When I first started programming C I also tended to just exit the program on getting a bad return code, because I'd never been taught how to properly handle errors - I'd always just replied on exceptions. Only later did I start to get bitten, and take proper care to handle errors sensibly and explicitly. As "go" is such a new language I wouldn't be surprised if a similar thing has happened to new programmers migrating over to it.
- cjslep 12y agoI find that I wind up "bubbling up" errors of my own to build a stack trace yet still retain control of my program, like: func LowLevelCode() (int, error) { // rest of body... if /* error condition */ { return 0, errors.New("LowLevelCode error: " + /* error description */) } } func MiddleWare() (interface{}, error) { // body of code... nCount, err := LowLevelCode() if err != nil { return nil, errors.New("MiddleWare error: " + err.Error()) } } func ApplicationCode() { // Do cool stuff... obj, err := MiddleWare() if err != nil { log.Println(err) // Prints "MiddleWare error: LowLevelCode error: " + ... // Handle on a high level, retry connections, etc. } } I'm hoping someone comes in and corrects me with a more elegant way of "building up your own stack trace" without having to do this manually and without panic.
- vardump 12y agoThis is what I used. http://golang.org/pkg/runtime/#Stack http://golang.org/pkg/runtime/#Stack
- NateDad 12y agoThere are a lot of error handling libraries out there. On Juju, we're working on one right now. This is the preliminary version: https://github.com/juju/errgo https://github.com/juju/errgo Basically you just call a function every time you would return an error, and it automatically records the file and line number and lets you add on some context to the message, plus it gives you the option to mask the type of the error (to hide implementation details from your callers). I wouldn't use errgo right now, it's still under heavy development, but it's similar to a lot of other error handling libraries out there.
- leaveyou 12y agoIf you search for "err != nil" it returns more: 13,153
- Jabbles 12y agoDoes anyone know the total number of lines of Go on Github? Without knowing that this number is meaningless.
- hornetblack 12y agoI find that a mustBeNil function is useful: func mustBeNil(err error) { if err != nil { panic(err) // or log.Fatal(err) } } or func mustBeNil(err error, msg string) { if err != nil { log.Fatalf("%s: %s", err, msg) } } ;Edit: brackets
- FiloSottile 12y agoPlease, pull that `panic(err)` line. It is widely accepted that is bad practice, and something you should not do. You panic when a program is expected to crash because of irrecoverable corruption. Only.
- sagichmal 12y ago> You panic when a program is expected to crash because of > irrecoverable corruption. Or programmer error.
- dscrd 12y agomustNotBe?
- NateDad 12y agoDon't panic in the middle of your code. You can occasionally use panic during init() when it indicates a programmer error. Otherwise, just return an error.
- dscrd 12y agoNote that the other two hip languages of the moment, Rust and Swift, also do not rely on exceptions for error management.
- arianvanp 12y agoI like erlang's model here. Fail often and fail fast and let the supervisor of that particular node just restart the thing. Bug fixing comes later when you inspect your logs. Do errors bubble up in actors in go like they do in erlang?
- tomp 12y agoHow is that different from exceptions, really? It's just less granular, but exactly what e.g. Django (a Python web framework) foes with exceptions (per-request, not per-process).
- pilif 12y agoThis higlights a point why I actually prefer exceptions to manually passing around error codes. If any of these calls to panic() are inside libraries, by using that library, you will put yourself into a position where your application will exit without giving you any recourse to fix it. I've seen too many C libraries doing this too - "mmh - I can't handle this right now - let me just call exit()" (last time this happened was inside a commerical library for reading excel files. If it was confronted with a file that was corrupt in some way, the library would call exit(), terminating my application server in the process. Thanks) With exceptions, a library can just throw but I as a consumer of the library at least get a chance to handle the error somehow and who knows - I might be able to find some way to still proceed and not crash. If I don't handle that exception, oh well, then I crash anyways, but that's not different from the library calling exit or panic. With exceptions, I also never risk that I (or a library I use) forgets to check for an exit code and proceeds to corrupt data (or crash way later at a seemingly unrelated point). When I fail to catch an exception, I my application dies. When I fail to check an error code, my application might possibly corrupt data. Just because implementing exceptions in compiled languages is difficult and messy (C++) and because exceptions are slow doesn't mean we shouldn't be using them. It means that we have to improve our language and OSes in order for them to work without hacks and quickly enough because, honestly, the value they provide to me is big enough to warrant a bit of research.
- Tobani 12y agoPanic is not exit. http://golang.org/pkg/os/#Exit http://golang.org/pkg/os/#Exit is exit Panic is throw. Recover is an isomorphism for catch. For the most part. You have to explicitly ignore error codes in go. (Not that you can't handle them incorrectly.)
- NateDad 12y ago"In order to ignore error codes in go, you must do so explicitly". - FTFY
- Tobani 12y agoYeah. The only caveat I'd give is a bit of a stretch. Not explicitly error handling, but you can lookup maps as: j := m["root"] or j, ok := m["root"] Again, this tangential to the subject at hand. But this is the main reason I qualify that statement.
- logicchains 12y agoI've found in some cases Go's style of error handling can actually make for great documentation: when every third line of code is something like if err != nil{ log.Printf("This specific operation %v went wrong because input %v was %v or was expected to be %v but wasn't", operationName, inputName, actualInput, expectedInput) } Then reading the error messages gives a pretty good idea of what's supposed to be happening at that point in the code.
- NateDad 12y agoHere's why Go's errors are better than exceptions: Multithreaded environments make exception handling next to impossible to do correctly. The stack trace only shows a single thread which is often useless without more context. Exceptions are per-thread, so handling has to be per-thread. Exceptions make the control flow of your program completely obscure. Any line that calls out to another function can cause this function to exit. If you're not wrapping every function call in a try/finally, you're probably doing it wrong (at which point it's even more verbose than go's error returns). Errors in Go aren't special. They're just values returned from a function. You have the whole language at your disposal to deal with them. I can write this in Go, and it's perfectly safe: f, err := os.Open(filename) if err != nil { return fmt.Errorf("Failed to open config file: %s", err) } err = callFunctionThatMightErrorOut() if err != nil { // handle err } f.Close() I know that my code will ALWAYS close that file handle. To do the same thing in a language with exceptions, you'd have to do something like this: File f try { try { f = os.Open(filename) } catch(Exception e) { throw new Exception(string.Format("Failed to open config file: %s", e.Message)) } try { callFunctionThatMightErrorOut() } catch(Exception e) { // handle error } } finally { if f != nil { f.Close() } } Now who's verbose? The reason people think Go exception handling is verbose is because they aren't really handling errors in their own code. Go makes it easy to handle errors in the right way.
- dllthomas 12y agoYour exception code ignores RAII, which is about the best argument for exceptions. The file should be closed as soon as the function returns for any reason, which (with a sensible design and properly implemented destructors) will be as soon as the function returns for any reason, meaning the outside try-finally is entirely unnecessary and the file is guaranteed freed even if you have multiple returns.
- vardump 12y agoGolang has defer. http://golang.org/doc/effective_go.html#defer http://golang.org/doc/effective_go.html#defer That's what grandparent post should have been using. f, err := os.Open(filename) if err != nil { return fmt.Errorf("Failed to open config file: %s", err) } defer f.Close() // This is going to be always executed when leaving this function err = callFunctionThatMightErrorOut() ... When writing C++, I wish I had defer over RAII. In C++, when interfacing with lower levels or C code, you need to first wrap everything in a class. Just so that you can use unique_ptr or shared_ptr etc. Or worse, write a scoping class. Ugh. Ugly and so many lines written for nothing...
- todd8 12y agoOne goal, while writing programs, is to construct programs that operate correctly (that is conform to the specifications that we have or imagine for the finished product). Complex control flows make reasoning about correctness difficult. Consider a simple loop: while x != y x = ... y = ... end while -- (A) At point (A) we know that x == y. This allows us to reason about what happens next in the program. Having a break statement within the loop means that we now have a more complex condition to consider at the end of the loop. while x != y x = ... if x == 0: break y = ... end while -- (B) At point (B) we know that x == y or x == 0. Once we introduce exceptions, we can end up outside the loop without knowing anything about the state of x and y (unless we examine a lot more code, possibly code quite remote from this little loop we are working on). go's requirement to laboriously check and handle every function return may make it easier to reason about x and y in our little example, but it comes at the cost of extra boilerplate error checking everywhere. This too hurts our ability to reason about the code we are writing by increasing verbosity, so I'm not sure that go's approach will work out much better than some form of exceptions mechanism. However, I welcome this approach to language design, keeping the language as simple as possible, but no simpler. Even if go error handling turns out in practice to be roughly as good as C++'s approach, it will be a big win for go because go ends up smaller, simpler, and easier to learn and master. It's difficult to assemble a team of C++ programmers to work on complex systems because it is so hard to find more than a handful of real C++ experts at a time. I feel safer recommending the use of go to a typical programming team than C++.