在Web应用程序中使用panic

huangapple go评论78阅读模式
英文:

Using panics in web application

问题

我正在使用Go编写我的Web应用程序。我想将大部分API错误转换为panic,并在更高级别的函数中捕获这些panic,将它们记录下来并返回错误页面给用户。

类似这样的代码:

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
    }
    // 记录日志并返回HTTP错误
}

func PanicIf(err error, httpStatus int, description string) {
    if err != nil {
        panic(MyPanicStruct{err: err, httpStatus: httpStatus, description: description})
    }
}

在我的实际代码中:

result, err := SomeApi(...)
PanicIf(err, http.StatusInternalServerError, "SomeApi")

在99%的情况下,当例如SQL服务器返回意外错误或文件在文件系统中丢失时,我无法做出合理的处理,我只想记录这种情况并向用户返回错误。因此,我看不出为什么我应该手动返回"err"来解开堆栈,实际上我会丢失堆栈跟踪和上下文,这将更加困难地找到错误原因。

我是否遗漏了什么,以至于这种方法不能很好地工作?似乎大多数Go文章都不推荐使用panic/recover,但我不明白为什么。它看起来就像Java(和类似的语言)中的良好的旧的抛出-捕获机制,对于Web应用程序来说非常完美。

英文:

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.

答案1

得分: 10

这是2014年11月4日Dave Cheney在“Error handling vs. exceptions redux”中讨论的内容:

C++异常在安全使用方面仍然像三十年前一样困难。当调用栈的任何部分都可能突然崩溃时,难怪有这么多C++开发团队禁止使用异常。

这与“Why Go gets exceptions right”(2012年,Go1.0之前,但至今仍然有效)有关:

Go确实有一个名为panic的机制,如果你用力眯起眼睛,你可能会认为panicthrow是一样的,但你错了。
当你抛出异常时,你把问题交给了调用者。

throw new SomeoneElsesProblem();

例如,在C++中,当无法将enum转换为其string等效项时,你可能会抛出异常;在Java中,当从字符串解析日期时也是如此。
在一个联网的世界中,每个来自网络的输入都必须被视为敌对的,将字符串解析为日期失败真的是异常吗?当然不是。

当你在Go中发生panic时,你是在发狂,这不是别人的问题,这是游戏结束。

panic("inconceivable")

panic对于程序总是致命的。
在发生panic时,你永远不要假设你的调用者可以解决问题。因此,panic只在异常情况下使用,即你的代码或集成你的代码的任何人都无法继续执行的情况下。

不在Go中包含异常的决策是其简洁性和正交性的一个例子。通过使用多个返回值和简单的约定,Go解决了让程序员知道何时出错的问题,并将panic保留给真正的异常情况。

其他使用err的方法在官方维基页面“Error handling and Go”中有讨论。

话虽如此,文章“Defer, Panic, and Recover”确实提到了panic的一个真实案例(json(d *decodeState) unmarshal方法),并补充说:

Go库的约定是,即使一个包在内部使用panic,其外部API仍然提供明确的错误返回值。

因此,如果你对panic的使用严格限制在内部,那么可能可行。

英文:

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

答案2

得分: 2

我个人的观点是,恐慌是魔鬼的作品,几乎不应该被使用。

话虽如此,我会在所有的http处理程序中包装一个panic recover,因为有很多事情(包括stdlib)会引发panic,你必须处理它 - 关闭你的web服务器不是正确的响应 - 我同意这一点。

我们所做的是用我们自己的错误包覆盖了标准库的错误包,这样当我们生成一个错误时,错误本身和回溯信息会被发送到一个站点,你可以在那里进行日志记录/跟踪。

例如,一个本地的http实现 ->
https://deferpanic.com/help/exception-handling/native

我相信这种方法可以让你既能拥有你的蛋糕,又能吃掉它,而不必求助于panic/recover。

英文:

So my own personal opinion is that panic is the work of the devil and should almost never be used at all.

Having said that I wrap all my http handlers in a panic recover as there are many things (stdlib included) that panic and you have to deal with it - taking down your webserver is not a proper response - agreed.

What we've done is override our errors package with our own so when we generate an error, the error itself, with the backtrace is shot up to a site so you can log/track it there.

For example a native http implementation ->
https://deferpanic.com/help/exception-handling/native

I believe this method allows you to have your cake and eat it too without resorting to panic/recover.

huangapple
  • 本文由 发表于 2014年11月4日 21:56:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/26736949.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定