3 ms·
For quite some time, I’ve taken to wrapping the most common err case with a simple function: func dieOnErr(err error) { if err != nil { lo
by abtinf 8y ago
For quite some time, I’ve taken to wrapping the most common err case with a simple function:
func dieOnErr(err error) {
if err != nil {
log.Fatal(err)
}
}
It makes the code much easier to read (due to efficient use of vertical space) and much less tedious to write:
dieOnErr(foo())
_, err := bar()
dieOnErr(err)
- skohan 8y agoWhat kind of application would you use this in? Just exiting on every error seems you're just punting the whole problem of error handling, and wrapping every fail-able function in another function doesn't seem like the best thing for readability either. Also this doesn't really solve the problem sited in the article: it requires that all your functions return exactly two values with the error in the second place. What if you need to return two non-error values?
- abtinf 8y agoI primarily use this in three areas: Command line tools: if any part of the command fails, you usually don't want to continue, unless it is a long-running batch process. In that case, I usually also write a logOnError() function to keep track of problematic work items. Servers: during server initialization, there are many circumstances where it doesn't make sense to continue, such as template compilation failure or failing to open the socket. Other: I use dieOnError anywhere it would otherwise make sense to use panic() - situations where continuing the program would mean running in an unknown state, like out-of-memory conditions. I've built systems with complex error handling before, but my teams have always run into the problem of trying to reason about complex, unpredictable program state. If the requirements can allow for the program to fail fast (as you say, "punting the whole problem"), then the code itself can be kept much simpler and easier to reason about. When errors do crop up, it is easier to look at logs to debug a simple program (or infrastructure issue) than to debug a complex program where you aren't sure how earlier errors affected state. In languages with exceptions, this is sort of the default - if you don't handle an exception, the program will crash. Often there will be a main level exception handler that logs otherwise uncaught errors. But a problem in Go is that programs are default-alive on errors. If you don't check the error status of every single function call that returns errors, then the program can get into unknown state. So in production code, especially code that just combines a few libraries, every function call ends up taking 3 or 4 lines (1 line of call and/or 3 lines of error checking). Regarding the number of return values, my dieOnError/logOnError only handles functions that return error and nothing else. If the function returns multiple values, I first call it, then call dieOnError with the err: foo, bar, err := baz() dieOnErr(err) //assume foo and bar are valid I suspect I could write a variadic error checking function that assumes the last parameter is error: func dieOnError(a ...interface{}) { //do some reflection magic? } func foo() (bar int, baz int, err error) { ... } //maybe needs some type assertions or something? bar, baz := dieOnError(foo())