写点什么

Go 的 Panics 处理

作者:baiyutang
  • 2021 年 11 月 19 日
  • 本文字数:2588 字

    阅读完需:约 8 分钟

Go 的 Panics 处理

译者:baiyutang

原文:https://www.digitalocean.com/community/tutorials/handling-panics-in-go


介绍

程序遇到的错误可以分为两大类:程序员预料到的错误和程序员没有预料到的错误。我们在前两篇关于错误处理的文章中介绍的 error 接口,主要处理我们在编写 Go 程序时所预期的错误。error 接口甚至允许我们检测到函数调用中发生罕见错误的可能性,因此我们可以在这些情况下作出适当的响应。


Panics 属于第二类错误,这是程序员始料未及的。这些不可预见的错误会导致程序自动终止并退出正在运行的 Go 程序。常见的错误往往是造成 Panics 的原因。在本教程中,我们将研究几种常见操作在 Go 中产生 Panics 的方法,以及避免这些 Panics 的方法。我们还将使用 defer 语句和 recover 函数来捕捉恐慌,以免它们有机会意外地终止正在运行的 Go 程序。

理解 Panic

在 Go 中有一些操作会自动返回 Panics 并停止程序。常见的操作包括索引超出容量的数组、执行类型断言、在空指针上调用方法、错误地使用互斥体以及尝试使用关闭的通道。这些情况大多数是由于编程时所犯的错误造成的,编译器在编译程序时无法检测到这些错误。


由于 Panics 包含了对解决问题有用的细节,开发人员通常使用 Panics 作为他们在程序开发过程中犯错误的提示。

越界 Panic

当您试图访问超出片长度或数组容量的索引时,Go 运行时将产生 Panics。


下面的例子犯了一个常见的错误,即尝试使用 len 内置函数返回的切片的长度来访问切片的最后一个元素。试着运行下面的代码,看看为什么会产生 Panics:

package main
import ( "fmt")
func main() { names := []string{ "lobster", "sea urchin", "sea cucumber", } fmt.Println("My favorite sea creature is:", names[len(names)])}
复制代码


这会有如下输出:

panic: runtime error: index out of range [3] with length 3
goroutine 1 [running]:main.main() /tmp/sandbox879828148/prog.go:13 +0x20
复制代码

Panic 解剖

Panics 由提示 Panics 原因的消息和堆栈跟踪组成,堆栈跟踪帮助您定位在代码中的何处产生了 Panics。


任何 Panics 的第一部分都是信息。它总是以字符串 panic: 开始,后面跟着一个根据引起 Panics 的原因而变化的字符串。上次演示的 Panics 传达了这样一个信息:

panic: runtime error: index out of range [3] with length 3
复制代码

Nil 接受者

Go 编程语言的指针指向运行时存在于计算机内存中的某种类型的特定实例。指针可以假设值为 nil,表示它们没有指向任何东西。当我们试图在一个 nil 指针上调用方法时,Go 运行时将产生 Panics。类似地,作为接口类型的变量在对其调用方法时也会产生 Panics 。要查看在这些情况下产生的 Panics,请尝试以下示例:

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()}
复制代码


Panics 将会是这样:

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:12main.main() /tmp/sandbox160713813/prog.go:18 +0x1a
复制代码

使用 panic 内建函数

我们也可以使用内置的 panic 函数来产生我们自己的 Panics。它接受一个字符串作为参数,这是 Panics 将产生的消息。通常,此消息比重写代码以返回错误要简单。此外,我们可以在我们自己的包中使用它来向开发人员表明,他们在使用我们包的代码时可能犯了错误。只要有可能,最佳实践是尽量将错误值返回给我们的包的消费者。

package main
func main() { foo()}
func foo() { panic("oh no!")}
复制代码


Panics 将会是这样:

panic: oh no!
goroutine 1 [running]:main.foo(...) /tmp/sandbox494710869/prog.go:8main.main() /tmp/sandbox494710869/prog.go:4 +0x40
复制代码

defer 函数

您的程序可能有必须正确清理的资源,即使在运行时正在处理 panic 时也是如此。Go 允许延迟执行函数调用,直到调用函数的函数完成执行。延迟函数甚至在出现恐慌时也会运行,并被用作一种安全机制,以防止 Panics 造成的混乱。函数被延迟是通过像往常一样调用它们,然后在整个语句前面加上 关键字,如defer sayHello()。运行此示例,查看在产生恐慌时如何打印消息:

package main
import "fmt"
func main() { defer func() { fmt.Println("hello from the deferred function!") }()
panic("oh no!")}
复制代码


此例的 Panics 将会是这样

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
复制代码

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 { return a / b}
复制代码

用 recover 检测 Panic

recover 函数依靠错误值来确定是否发生了恐慌。因为 panic 函数的参数是一个空接口,所以它可以是任何类型。任何接口类型(包括空接口)的零值都是 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}
复制代码


总结

我们已经看到了在 Go 中许多产生 Panics 的方法,以及如何使用内置的 recover 恢复 Panics。虽然您自己可能并不一定使用 panic,但是从 Panics 中适当恢复是使 Go 应用程序具备生产能力的重要步骤。

发布于: 3 小时前阅读数: 13
用户头像

baiyutang

关注

广州 2017.12.13 加入

Microservices | Golang | Cloud Nitive | “Smart work,Not hard”

评论

发布
暂无评论
Go 的 Panics 处理