Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using panics in web application

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.

like image 829
vbezhenar Avatar asked Dec 14 '22 18:12

vbezhenar


1 Answers

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 its string 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")

panics 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.

like image 124
VonC Avatar answered Dec 30 '22 00:12

VonC