I'm writing my web application with Go. I want to convert most of the errors from API to panics, and then catch those panics in higher-level function, log them and return error page to user.
Something like this:
func Handler(body func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(responseWriter http.ResponseWriter, request *http.Request) {
defer recoverIfPanic(responseWriter, request)
body(responseWriter, request)
}
}
func recoverIfPanic(responseWriter http.ResponseWriter, request *http.Request) {
reason := recover()
if reason == nil {
return
}
// log and return http error
}
func PanicIf(err error, httpStatus int, description string) {
if error != nil {
panic(MyPanicStruct{err: err, httpStatus: httpStatus, description: description})
}
}
and in my actual code
result, err := SomeApi(...)
PanicIf(err, http.StatusInternalServerError, "SomeApi")
In 99% cases I can't do anything reasonable when, e.g. SQL server returns unexpected error or file is missing from filesystem, all I want is to log this situation and return error to user. So I can't see any reason I should return "err" unwinding stack manually, actually I'll lost stacktrace and context and it would be more difficult to find error reason.
Is there anything I miss so this approach won't work well? It seems that most Go articles recommend against using panic/recover, but I don't see why. It looks exactly like good old throw-catch mechanism in Java (and similar languages) and it works perfectly for web applications.
Is there anything I miss so this approach won't work well?
That is discussed today (!) Nov. 4th, 2014, by Dave Cheney in "Error handling vs. exceptions redux"
C++ exceptions, remain as difficult to use safely as they did three decades ago. When any part of your call stack can explode without warning, it is no wonder so many C++ shops mandate that exceptions not be used.
It refers to "Why Go gets exceptions right" (2012, pre Go1.0, but still valid today):
Go does have a facility called
panic
, and if you squint hard enough, you might imagine that panic is the same as throw, but you’d be wrong.
When you throw and exception you’re making it the caller’s problem
throw new SomeoneElsesProblem();
For example in C++ you might throw an exception when you can’t convert from an
enum
to itsstring
equivalent, or in Java when parsing a date from a string.
In an internet connected world, where every input from a network must be considered hostile, is the failure to parse a string into a date really exceptional? Of course not.When you panic in Go, you’re freaking out, it’s not someone elses problem, it’s game over man.
panic("inconceivable")
panic
s are always fatal to your program.
In panicing you never assume that your caller can solve the problem. Hence panic is only used in exceptional circumstances, ones where it is not possible for your code, or anyone integrating your code to continue.The decision to not include exceptions in Go is an example of its simplicity and orthogonality. Using multiple return values and a simple convention, Go solves the problem of letting programmers know when things have gone wrong and reserves panic for the truly exceptional.
Other approaches, using err
are discussed in the official wiki page "Error handling and Go".
That being said, the article "Defer, Panic, and Recover" do mention a real case for panic (json
package, (d *decodeState) unmarshal
method), and add:
The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.
So if your use of panic is strictly an internal one, that could work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With