为什么Go语言除了错误处理之外还添加了panic和recover呢?

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

Why did Go add panic and recover in addition to error handling?

问题

为什么Go语言最终采用了panic/recover的异常处理机制,尽管该语言非常注重错误码的使用和符合惯用法?Go语言的设计者们预见到了哪些情况无法通过错误码处理,从而需要使用panic/recover呢?

我知道惯例上说要限制panic/recover的使用,但是运行时是否也对它们进行了限制,使它们不能像C++中的通用throw/catch一样使用?

英文:

Why did Go end up adopting exception handling with panic/recover, when the language is so idiomatic and a strong advocate of error codes? What scenarios did the designers of Go envision not handled by error codes and necessitate panic/recover?

I understand convention says limit panic/recover, but does the runtime also limit them in ways that they can't be used as general throw/catch in C++?

答案1

得分: 18

一些历史:

在Go的早期版本(1.0之前),没有recover()函数。调用panic()函数会终止应用程序,没有任何方法可以停止它。

我找到了导致添加recover()函数的原始讨论,你可以在golang-nuts讨论论坛上阅读它:

类似异常机制的提案

请注意:这个讨论是在2010年3月25日之前进行的,非常冗长(共有150个帖子,分为6页)。

最终在2010-03-30添加了该功能:

> 这个版本包含了三个语言变化:

> 3. 添加了用于报告和恢复失败的函数panicrecover到规范中:
> http://golang.org/doc/go_spec.html#Handling_panics
> 在相关的变化中,panicln被移除,panic现在是一个单参数函数。Panic和recover被gc编译器识别,但新的行为尚未实现。


在Go中,多返回值和约定提供了一种更清晰的处理错误的方式。

然而,并不意味着在某些(罕见)情况下,panic-recover机制没有用处。

引用官方FAQ:为什么Go没有异常?中的一段话:

> Go还有一些内置函数用于信号和从真正异常情况中恢复。恢复机制仅在函数的状态在错误后被撤销时执行,这足以处理灾难,但不需要额外的控制结构,并且当使用得当时,可以产生清晰的错误处理代码。

下面是一个“现实生活”中的例子,说明何时/如何使用它:引用博客文章Defer,Panic和Recover中的内容:

> 一个真实的例子是Go标准库中的json包。它使用一组递归函数解码JSON编码的数据。当遇到格式错误的JSON时,解析器调用panic函数将堆栈展开到顶层函数调用,然后从panic中恢复并返回适当的错误值(请参阅decode.go中decodeState类型的'error'和'unmarshal'方法)。

另一个例子是当你编写调用用户提供的函数的代码(例如包)时。你不能相信提供的函数不会发生panic。一种方式是不处理它(让panic继续传播),或者你可以通过从panic中恢复来“保护”你的代码。标准库中提供的http服务器就是一个很好的例子:你提供的函数(handlershandler functions)将由服务器调用,如果你的处理函数发生panic,服务器将从panic中恢复,不会导致整个应用程序崩溃。

如何使用它们:

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

相关和有用的阅读材料:

http://blog.golang.org/defer-panic-and-recover

http://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right

https://golang.org/doc/faq#exceptions

http://evanfarrer.blogspot.co.uk/2012/05/go-programming-language-has-exceptions.html

英文:

Some history:

In the early days of Go (before version 1.0) there was no recover(). A call to panic() would terminate an application without any way to stop that.

I've found the original discussion that led to adding recover(), you can read it on the golang-nuts discussion forum:

Proposal for an exception-like mechanism

Beware: the discussion dates back to March, 25, 2010, and it is quite exhausting and long (150 posts through 6 pages).

Eventually it was added on 2010-03-30:

> This release contains three language changes:

> 3. The functions panic and recover, intended for reporting and recovering from
failure, have been added to the spec:
> http://golang.org/doc/go_spec.html#Handling_panics
> In a related change, panicln is gone, and panic is now a single-argument
function. Panic and recover are recognized by the gc compilers but the new
behavior is not yet implemented.


Multi-return values and conventions provide a cleaner way to handle errors in Go.

That does not mean however that in some (rare) cases the panic-recover is not useful.

Quoting from the official FAQ: Why does Go not have exceptions?

> Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function's state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.

Here is a "real-life" example for when/how it can be useful: quoting from blog post Defer, Panic and Recover:

> For a real-world example of panic and recover, see the json package from the Go standard library. It decodes JSON-encoded data with a set of recursive functions. When malformed JSON is encountered, the parser calls panic to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'unmarshal' methods of the decodeState type in decode.go).

Another example is when you write code (e.g. package) which calls a user-supplied function. You can't trust the provided function that it won't panic. One way is not to deal with it (let the panic wind up), or you may choose to "protect" your code by recovering from those panics. A good example of this is the http server provided in the standard library: you are the one providing functions that the server will call (the handlers or handler functions), and if your handlers panic, the server will recover from those panics and not let your complete application die.

How you should use them:

> The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.

Related and useful readings:

http://blog.golang.org/defer-panic-and-recover

http://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right

https://golang.org/doc/faq#exceptions

http://evanfarrer.blogspot.co.uk/2012/05/go-programming-language-has-exceptions.html

答案2

得分: 1

我认为你的问题是由你所持有的心智模型造成的,这个心智模型是由流行的主流语言(如Java、C++、C#、PHP等)灌输的,这些语言在处理异常方面存在问题。

事实上,异常本身并不是一个错误的概念,但滥用异常来处理实际上并不是异常的情况是错误的。我个人最讨厌的是Java的文件系统处理API(以及几乎完全照搬Java的.NET):为什么文件不存在时打开文件失败会导致异常?文件系统是一个本质上存在竞争条件的介质,并且被规定为存在竞争条件,因此在打开文件进行读取之前,确保文件存在的唯一正确方法是直接打开文件,然后检查“文件不存在”错误:文件不存在的情况根本不是异常。

因此,Go语言明确区分了异常情况和普通的错误情况。
Go语言在处理错误方面的立场是“错误是值”,因此正常预期的错误被处理为值,而panic()用于处理异常情况。一个简单的例子:

  • 尝试解引用一个nil指针会导致panic

    原因是你的代码继续执行并尝试解引用一个不指向任何值的指针。紧接着的代码明显期望该值可用——作为解引用操作的结果。因此,控制流无法以任何明智的方式正常进行,这就是一个异常情况:在正确的程序中,不会发生对nil指针的解引用。

  • TCP流的远程端突然关闭了流的一侧,并且下一次尝试从中读取导致了错误。

    这是一个非常正常的情况:不能合理地期望TCP会话是牢不可破的:网络中断、数据包丢失、意外的停电是可能发生的,我们必须为远程对等方意外关闭流做好准备。

panic()的一个小变化是,Go语言不强制你盲目地遵循某些教条,你可以自由地在特定情况下“滥用”panic/recover,比如通过使用特定类型的错误值进行panic,并在执行recover的地方进行检查,以从深层嵌套的处理循环中跳出。

进一步阅读:

英文:

I think that your question is the result of a mental model you maintain which is instilled by popular mainstream languages such as Java, C++, C#, PHP and zillions of other which simply got exceptions wrong.

The thing is, exceptions per se are not a wrong concept but abusing them to handle cases which are, in reality, not exceptional is. My personal pet peeve is the filesystem handling API of Java (and .NET, which copied that of Java almost verbatim): why on Earth failure to open a file results in an exception if that file does not exist? The filesystem is an inherently racy medium, and is specified to be racy, so the only correct way to ensure a file exists before opening it for reading is to just open it and then check for "file does not exist" error: the case of the file not existing is not exceptional at all.

Hence Go clearly separates exceptional cases from plain normal errors.
The motto of the stance Go maintains on handling errors is "Errors are values" and thus normal expected errors are handled as values, and panic() serves to handle exceptional cases. A good simple example:

  • An attempt to dereference a nil pointer results in a panic.

    The rationale: your code went on and tried to dereference a pointer which does not point to any value. The immediately following code clearly expects that value to be available—as the result of the dereferencing operation. Hence the control flow clearly can't proceed normally in any sensible way, and that's why this is an exceptional situation: in a correct program, dereferencing of nil pointers cannot occur.

  • The remote end of a TCP stream abruptly closed its side of the stream and the next attempt to read from it resulted in an error.

    That's pretty normal situation: one cannot sensbily expect a TCP session to be rock-solid: network outages, packet drops, unexpected power blackouts do occur, and we have to be prepared for unexpected stream closures by our remote peers.

A minor twist to panic() is that Go does not force you to blindly follow certain dogmas, and you can freely "abuse" panic/recover in tightly-controlled specific cases such as breaking out from a deeply-nested processing loop by panicking with an error value of a specific type known and checked for at the site performing recover.

Further reading:

答案3

得分: 0

我认为原因是并发模型。Go语言在本质上是一种高度并发的语言,并且具有核心语法。当某些计算局部化到并发进程失败时,但整个系统仍然可以正常工作,这可以被视为正常情况。在我看来,panic和recover是关于处理失败,而不是异常。

英文:

I think the reason is concurrency model. Go is highly concurrent language in nature and core syntax. Scenario when some computation localized to concurrent process failure, but hole system continue to work can be viewed as normal. To my mind panic and recover are about failure handling, not exceptions.

答案4

得分: 0

因为这样更强大,可以创建更具弹性的程序。

在C语言中,无法从空指针解引用中恢复。

在Go语言中,可以使用recover来捕获它。

例如,在Go语言中可以创建一个HTTP服务器,在处理请求时捕获空指针解引用,并返回错误500而不是使整个服务器崩溃。

英文:

Because this is more powerful as it allows to create more resilient programs.

In C you can't recover from a NULL pointer dereference.

In Go you can catch it with recover.

This allows for example to create in Go an HTTP server that can catch a NULL pointer dereference when handling a request and return an error 500 instead of crashing the whole server.

huangapple
  • 本文由 发表于 2016年2月15日 22:54:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/35412449.html
匿名

发表评论

匿名网友

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

确定