介绍 (Introduction)
Errors that a program encounters fall into two broad categories: those the programmer has anticipated and those the programmer has not. The error
interface that we have covered in our previous two articles on error handling largely deal with errors that we expect as we are writing Go programs. The error
interface even allows us to acknowledge the rare possibility of an error occurring from function calls, so we can respond appropriately in those situations.
程序遇到的错误分为两大类:程序员所预期的错误和程序员所没有的错误。 我们在前两篇有关错误处理的文章中介绍的error
接口在很大程度上处理了我们在编写Go程序时期望的错误。 error
界面甚至允许我们确认函数调用发生错误的可能性很小,因此我们可以在这些情况下做出适当的响应。
Panics fall into the second category of errors, which are unanticipated by the programmer. These unforeseen errors lead a program to spontaneously terminate and exit the running Go program. Common mistakes are often responsible for creating panics. In this tutorial, we’ll examine a few ways that common operations can produce panics in Go, and we’ll also see ways to avoid those panics. We’ll also use defer
statements along with the recover
function to capture panics before they have a chance to unexpectedly terminate our running Go programs.
恐慌属于程序员无法预料的第二类错误。 这些无法预料的错误导致程序自发终止并退出正在运行的Go程序。 常见错误通常是造成恐慌的原因。 在本教程中,我们将研究几种常见的操作可以在Go中产生恐慌的方法,并且还将看到避免这些恐慌的方法。 我们还将使用defer
语句以及recover
函数来捕获紧急事件,以免它们有可能意外终止正在运行的Go程序。
了解恐慌 (Understanding Panics)
There are certain operations in Go that automatically return panics and stop the program. Common operations include indexing an array beyond its capacity, performing type assertions, calling methods on nil pointers, incorrectly using mutexes, and attempting to work with closed channels. Most of these situations result from mistakes made while programming that the compiler has no ability to detect while compiling your program.
Go中有某些操作会自动返回恐慌并停止程序。 常见的操作包括索引超出其容量的数组 ,执行类型声明,在nil指针上调用方法,不正确地使用互斥锁以及尝试使用封闭通道。 这些情况中的大多数是由于编程时出错而导致的,编译器在编译程序时无法检测到这些错误。
Since panics include detail that is useful for resolving an issue, developers commonly use panics as an indication that they have made a mistake during a program’s development.
由于紧急情况包括对解决问题有用的细节,因此开发人员通常使用紧急情况来表示他们在程序开发过程中犯了一个错误。
惊慌失措 (Out of Bounds Panics)
When you attempt to access an index beyond the length of a slice or the capacity of an array, the Go runtime will generate a panic.
当您尝试访问超出切片长度或数组容量的索引时,Go运行时将产生恐慌。
The following example makes the common mistake of attempting to access the last element of a slice using the length of the slice returned by the len
builtin. Try running this code to see why this might produce a panic:
以下示例犯了一个常见错误,即尝试使用len
内置函数返回的切片的长度来访问切片的最后一个元素。 尝试运行以下代码以查看为什么可能会引起恐慌:
package main
import (
"fmt"
)
func main() {
names := []string{
"lobster",
"sea urchin",
"sea cucumber",
}
fmt.Println("My favorite sea creature is:", names[len(names)])
}
This will have the following output:
这将具有以下输出:
Output
panic: runtime error: index out of range [3] with length 3
goroutine 1 [running]:
main.main()
/tmp/sandbox879828148/prog.go:13 +0x20
The name of the panic’s output provides a hint: panic: runtime error: index out of range
. We created a slice with three sea creatures. We then tried to get the last element of the slice by indexing that slice with the length of the slice using the len
builtin function. Remember that slices and arrays are zero-based; so the first element is zero and the last element in this slice is at index 2
. Since we try to access the slice at the third index, 3
, there is no element in the slice to return because it is beyond the bounds of the slice. The runtime has no option but to terminate and exit since we have asked it to do something impossible. Go also can’t prove during compilation that this code will try to do this, so the compiler cannot catch this.
紧急情况的输出名称提供了一个提示: panic: runtime error: index out of range
。 我们用三个海洋生物创建了一个切片。 然后,我们尝试使用len
内置函数通过使用切片的长度索引该切片来获取切片的最后一个元素。 请记住,切片和数组是从零开始的。 因此该切片中的第一个元素为零,而最后一个元素在索引2
。 由于我们尝试访问第三个索引3
的切片,因此切片中没有要返回的元素,因为它超出了切片的范围。 运行时别无选择,只能终止并退出,因为我们已经要求它做一些不可能的事情。 Go也无法在编译期间证明该代码将尝试执行此操作,因此编译器无法捕获此代码。
Notice also that the subsequent code did not run. This is because a panic is an event that completely halts the execution of your Go program. The message produced contains multiple pieces of information helpful for diagnosing the cause of the panic.
还请注意,后续代码未运行。 这是因为紧急事件是一个完全停止Go程序执行的事件。 产生的消息包含多条信息,有助于诊断紧急情况的原因。
恐慌的解剖 (Anatomy of a Panic)
Panics are composed of a message indicating the cause of the panic and a stack trace that helps you locate where in your code the panic was produced.
紧急情况由指示紧急情况原因的消息和堆栈跟踪组成 ,可帮助您确定在代码中产生紧急情况的位置。
The first part of any panic is the message. It will always begin with the string panic:
, which will be followed with a string that varies depending on the cause of the panic. The panic from the previous exercise has the message:
恐慌的第一部分是消息。 它将始终以字符串panic:
开头,其后是一个字符串,该字符串根据出现紧急情况的原因而有所不同。 上一练习的恐慌提示信息:
panic: runtime error: index out of range [3] with length 3
The string runtime error:
following the panic:
prefix tells us that the panic was generated by the language runtime. This panic is telling us that we attempted to use an index [3]
that was out of range of the slice’s length 3
.
字符串runtime error:
紧随panic:
前缀后,告诉我们该panic由语言运行时生成。 这种恐慌告诉我们,我们尝试使用的索引[3]
超出了切片长度3
的范围。
Following this message is the stack trace. Stack traces form a map that we can follow to locate exactly what line of code was executing when the panic was generated, and how that code was invoked by earlier code.
该消息之后是堆栈跟踪。 堆栈跟踪形成一个映射,我们可以遵循该映射来精确定位在生成恐慌时正在执行的代码行,以及早期代码如何调用该代码。
goroutine 1 [running]:
main.main()
/tmp/sandbox879828148/prog.go:13 +0x20
This stack trace, from the previous example, shows that our program generated the panic from the file /tmp/sandbox879828148/prog.go
at line number 13. It also tells us that this panic was generated in the main()
function from the main
package.
上一个示例中的堆栈跟踪显示,我们的程序从文件/tmp/sandbox879828148/prog.go
在第13行生成了panic。它还告诉我们,该panic是在main()
函数中从main
包。
The stack trace is broken into separate blocks—one for each goroutine in your program. Every Go program’s execution is accomplished by one or more goroutines that can each independently and simultaneously execute parts of your Go code. Each block begins with the header goroutine X [state]:
. The header gives the ID number of the goroutine along with the state that it was in when the panic occurred. After the header, the stack trace shows the function that the program was executing when the panic happened, along with the filename and line number where the function executed.
堆栈跟踪分为多个块-程序中的每个goroutine一个块。 每个Go程序的执行都由一个或多个goroutine完成,每个goroutine可以独立并同时执行部分Go代码。 每个块都以头文件goroutine X [state]:
开头。 标头提供了goroutine的ID号以及发生恐慌时的状态。 在标题之后,堆栈跟踪显示了发生紧急情况时程序正在执行的功能,以及执行该功能的文件名和行号。
The panic in the previous example was generated by an out-of-bounds access to a slice. Panics can also be generated when methods are called on pointers that are unset.
上一个示例中的恐慌是由于对切片的越界访问而产生的。 当在未设置的指针上调用方法时,也会生成恐慌。
零接收器 (Nil Receivers)
The Go programming language has pointers to refer to a specific instance of some type existing in the computer’s memory at runtime. Pointers can assume the value nil
indicating that they are not pointing at anything. When we attempt to call methods on a pointer that is nil
, the Go runtime will generate a panic. Similarly, variables that are interface types will also produce panics when methods are called on them. To see the panics generated in these cases, try the following example:
Go编程语言具有指针,用于引用运行时计算机内存中存在的某种类型的特定实例。 指针可以假定值为nil
表示它们没有指向任何对象。 当我们尝试在nil
指针上调用方法时,Go运行时将产生恐慌。 同样,作为接口类型的变量在调用方法时也会产生恐慌。 要查看在这些情况下生成的紧急情况,请尝试以下示例:
package main
import (
"fmt"
)
type Shark struct {
Name string
}
func (s *Shark) SayHello() {
fmt.Println("Hi! My name is", s.Name)
}
func main() {
s := &Shark{"Sammy"}
s = nil
s.SayHello()
}
The panics produced will look like this:
产生的紧急情况如下所示:
Output
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xdfeba]
goroutine 1 [running]:
main.(*Shark).SayHello(...)
/tmp/sandbox160713813/prog.go:12
main.main()
/tmp/sandbox160713813/prog.go:18 +0x1a
In this example, we defined a struct called Shark
. Shark
has one method defined on its pointer receiver called SayHello
that will print a greeting to standard out when called. Within the body of our main
function, we create a new instance of this Shark
struct and request a pointer to it using the &
operator. This pointer is assigned to the s
variable. We then reassign the s
variable to the value nil
with the statement s = nil
. Finally we attempt to call the SayHello
method on the variable s
. Instead of receiving a friendly message from Sammy, we receive a panic that we have attempted to access an invalid memory address. Because the s
variable is nil
, when the SayHello
function is called, it tries to access the field Name
on the *Shark
type. Because this is a pointer receiver, and the receiver in this case is nil
, it panics because it can’t dereference a nil
pointer.
在此示例中,我们定义了一个名为Shark
的结构。 Shark
在其指针接收器上定义了一个名为SayHello
方法,该方法在被调用时将向标准输出打印问候语。 在main
函数的主体内,我们创建此Shark
结构的新实例,并使用&
运算符请求指向它的指针。 该指针分配给s
变量。 然后,我们重新分配s
变量的值nil
的发言s = nil
。 最后,我们尝试对s
调用SayHello
方法。 我们没有收到Sammy的友好消息,而是收到了试图访问无效内存地址的恐慌。 因为s
变量为nil
,所以在调用SayHello
函数时,它将尝试访问*Shark
类型的Name
字段。 因为这是一个指针接收器,并且在这种情况下接收器为nil
,所以它会死机,因为它无法取消引用nil
指针。
While we have set s
to nil
explicitly in this example, in practice this happens less obviously. When you see panics involving nil pointer dereference
, be sure that you have properly assigned any pointer variables that you may have created.
尽管在此示例中将s
显式设置为nil
,但实际上这种情况不太明显。 当您看到涉及nil pointer dereference
恐慌时,请确保已正确分配了可能创建的所有指针变量。
Panics generated from nil pointers and out-of-bounds accesses are two commonly occurring panics generated by the runtime. It is also possible to manually generate a panic using a builtin function.
由nil指针和越界访问生成的恐慌是运行时生成的两种常见的恐慌。 也可以使用内置函数手动生成紧急情况。
使用panic
内置函数 (Using the panic
Builtin Function)
We can also generate panics of our own using the panic
built-in function. It takes a single string as an argument, which is the message the panic will produce. Typically this message is less verbose than rewriting our code to return an error. Furthermore, we can use this within our own packages to indicate to developers that they may have made a mistake when using our package’s code. Whenever possible, best practice is to try to return error
values to consumers of our package.
我们还可以使用panic
内置函数生成自己的panic
。 它以单个字符串作为参数,这是恐慌将产生的消息。 通常,此消息不如重写我们的代码以返回错误那么冗长。 此外,我们可以在我们自己的软件包中使用它来向开发人员指示他们在使用我们的软件包的代码时可能犯了一个错误。 只要有可能,最佳实践就是尝试将error
值返回给我们程序包的使用者。
Run this code to see a panic generated from a function called from another function:
运行以下代码,查看从另一个函数调用的函数生成的恐慌:
package main
func main() {
foo()
}
func foo() {
panic("oh no!")
}
The panic output produced looks like:
产生的紧急输出如下所示:
Output
panic: oh no!
goroutine 1 [running]:
main.foo(...)
/tmp/sandbox494710869/prog.go:8
main.main()
/tmp/sandbox494710869/prog.go:4 +0x40
Here we define a function foo
that calls the panic
builtin with the string "oh no!"
. This function is called by our main
function. Notice how the output has the message panic: oh no!
and the stack trace shows a single goroutine with two lines in the stack trace: one for the main()
function and one for our foo()
function.
在这里,我们定义了一个函数foo
,它使用字符串"oh no!"
来调用panic
内置函数"oh no!"
。 此函数由我们的main
函数调用。 注意输出如何显示消息panic: oh no!
堆栈跟踪显示了一个goroutine,在堆栈跟踪中有两行:一条用于main()
函数,另一条用于我们的foo()
函数。
We’ve seen that panics appear to terminate our program where they are generated. This can create problems when there are open resources that need to be properly closed. Go provides a mechanism to execute some code always, even in the presence of a panic.
我们已经看到,恐慌似乎终止了生成它们的程序。 当存在需要适当关闭的开放资源时,这可能会造成问题。 Go提供了一种机制,即使在出现紧急情况时也可以始终执行某些代码。
递延功能 (Deferred Functions)
Your program may have resources that it must clean up properly, even while a panic is being processed by the runtime. Go allows you to defer the execution of a function call until its calling function has completed execution. Deferred functions run even in the presence of a panic, and are used as a safety mechanism to guard against the chaotic nature of panics. Functions are deferred by calling them as usual, then prefixing the entire statement with the defer
keyword, as in defer sayHello()
. Run this example to see how a message can be printed even though a panic was produced:
您的程序可能具有必须正确清理的资源,即使在运行时正在处理紧急情况时也是如此。 Go允许您将函数调用的执行推迟到其调用函数完成执行为止。 延迟功能甚至在出现紧急情况时也可以运行,并用作防止紧急情况混乱的安全机制。 通过照常调用函数来延迟函数,然后在整个语句前加上defer
关键字,如defer sayHello()
。 运行以下示例以查看即使出现紧急情况也如何打印消息:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("hello from the deferred function!")
}()
panic("oh no!")
}
The output produced from this example will look like:
此示例产生的输出如下所示:
Output
hello from the deferred function!
panic: oh no!
goroutine 1 [running]:
main.main()
/Users/gopherguides/learn/src/github.com/gopherguides/learn//handle-panics/src/main.go:10 +0x55
Within the main
function of this example, we first defer
a call to an anonymous function that prints the message "hello from the deferred function!"
. The main
function then immediately produces a panic using the panic
function. In the output from this program, we first see that the deferred function is executed and prints its message. Following this is the panic we generated in main
.
在此示例的main
函数中,我们首先将对匿名函数的调用defer
,该匿名函数将打印消息"hello from the deferred function!"
。 然后, main
功能会使用panic
功能立即产生panic
。 在该程序的输出中,我们首先看到deferred函数已执行并打印其消息。 接下来是我们在main
产生的恐慌。
Deferred functions provide protection against the surprising nature of panics. Within deferred functions, Go also provides us the opportunity to stop a panic from terminating our Go program using another built-in function.
递延功能可防止出现紧急情况的意外情况。 在延迟函数中,Go还为我们提供了使用另一个内置函数阻止恐慌终止我们的Go程序的机会。
处理恐慌 (Handling Panics)
Panics have a single recovery mechanism—the recover
builtin function. This function allows you to intercept a panic on its way up through the call stack and prevent it from unexpectedly terminating your program. It has strict rules for its use, but can be invaluable in a production application.
紧急事件具有单一的恢复机制-内置recover
功能。 使用此功能,您可以拦截在整个调用堆栈中发生的紧急情况,并防止它意外终止程序。 它有严格的使用规则,但在生产应用程序中可能是无价的。
Since it is part of the builtin
package, recover
can be called without importing any additional packages:
既然是部分builtin
包, recover
可以称之为而不导入任何其他软件包:
package main
import (
"fmt"
"log"
)
func main() {
divideByZero()
fmt.Println("we survived dividing by zero!")
}
func divideByZero() {
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
fmt.Println(divide(1, 0))
}
func divide(a, b int) int {
return a / b
}
This example will output:
此示例将输出:
Output
2009/11/10 23:00:00 panic occurred: runtime error: integer divide by zero
we survived dividing by zero!
Our main
function in this example calls a function we define, divideByZero
. Within this function, we defer
a call to an anonymous function responsible for dealing with any panics that may arise while executing divideByZero
. Within this deferred anonymous function, we call the recover
builtin function and assign the error it returns to a variable. If divideByZero
is panicking, this error
value will be set, otherwise it will be nil
. By comparing the err
variable against nil
, we can detect if a panic occurred, and in this case we log the panic using the log.Println
function, as though it were any other error
.
在此示例中,我们的main
函数调用了一个我们定义的函数divideByZero
。 在此函数内,我们divideByZero
匿名函数的调用defer
到一个匿名函数,该函数负责处理执行divideByZero
可能出现的任何紧急情况。 在此延迟的匿名函数中,我们调用了recover
内置函数并将返回的错误分配给变量。 如果divideByZero
恐慌状态,则将设置此error
值,否则将为nil
。 通过将err
变量与nil
进行比较,我们可以检测是否发生了紧急情况,在这种情况下,我们可以使用log.Println
函数记录该紧急情况,就好像它是其他任何error
。
Following this deferred anonymous function, we call another function that we defined, divide
, and attempt to print its results using fmt.Println
. The arguments provided will cause divide
to perform a division by zero, which will produce a panic.
在此延迟的匿名函数之后,我们调用定义, divide
并尝试使用fmt.Println
打印其结果的另一个函数。 提供的参数将导致divide
零的除法,这将产生恐慌。
In the output to this example, we first see the log message from the anonymous function that recovers the panic, followed by the message we survived dividing by zero!
. We have indeed done this, thanks to the recover
builtin function stopping an otherwise catastrophic panic that would terminate our Go program.
在该示例的输出中,我们首先看到来自匿名函数的日志消息,该消息恢复了紧急状态,然后是we survived dividing by zero!
的消息we survived dividing by zero!
。 我们确实做到了这一点,这要归功于recover
内置函数停止了原本会终止Go程序的灾难性恐慌。
The err
value returned from recover()
is exactly the value that was provided to the call to panic()
. It’s therefore critical to ensure that the err
value is only nil when a panic has not occurred.
从recover()
返回的err
值恰好是提供给panic()
调用的值。 因此,至关重要的是确保在未发生紧急情况时err
值仅为零。
通过recover
检测紧急情况 (Detecting Panics with recover
)
The recover
function relies on the value of the error to make determinations as to whether a panic occurred or not. Since the argument to the panic
function is an empty interface, it can be any type. The zero value for any interface type, including the empty interface, is nil
. Care must be taken to avoid nil
as an argument to panic
as demonstrated by this example:
recover
功能依赖于错误的值来确定是否发生了紧急情况。 由于panic
函数的参数是一个空接口,因此它可以是任何类型。 任何接口类型(包括空接口)的nil
值为nil
。 如以下示例所示,必须注意避免将nil
作为panic
的论点:
package main
import (
"fmt"
"log"
)
func main() {
divideByZero()
fmt.Println("we survived dividing by zero!")
}
func divideByZero() {
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
fmt.Println(divide(1, 0))
}
func divide(a, b int) int {
if b == 0 {
panic(nil)
}
return a / b
}
This will output:
这将输出:
Output
we survived dividing by zero!
This example is the same as the previous example involving recover
with some slight modifications. The divide
function has been altered to check if its divisor, b
, is equal to 0
. If it is, it will generate a panic using the panic
builtin with an argument of nil
. The output, this time, does not include the log message showing that a panic occurred even though one was created by divide
. This silent behavior is why it is very important to ensure that the argument to the panic
builtin function is not nil
.
此示例与涉及recover
的前一示例相同,但略有修改。 divide
功能已更改为检查其除数b
是否等于0
。 如果是,它将使用参数为nil
的panic
内置函数生成panic。 这次的输出中,不包括显示发生恐慌的日志消息,即使它是由divide
创建的。 这种无声的行为是为什么确保panic
内置函数的参数不为nil
非常重要的原因。
结论 (Conclusion)
We have seen a number of ways that panic
s can be created in Go and how they can be recovered from using the recover
builtin. While you may not necessarily use panic
yourself, proper recovery from panics is an important step of making Go applications production-ready.
我们已经看到了许多的办法是panic
S能在围棋创建以及如何使用该恢复recover
内建命令。 虽然您不一定必须使用panic
,但从紧急状态中正确恢复是使Go应用程序可投入生产的重要一步。
You can also explore our entire How To Code in Go series.
您也可以探索我们的整个“如何编写代码”系列 。
翻译自: https://www.digitalocean.com/community/tutorials/handling-panics-in-go