Go语言入门20:错误与异常
Table of Contents
Go 语言入门基础学习笔记之 Go 语言的错误与异常处理
错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error 类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用 errors.New
可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
在下面的例子中,我们在调用 Sqrt
的时候传递的一个负数,然后就得到了 non-nil
的 error
对象,将此对象与 nil
比较,结果为 true
,所以 fmt.Println
(fmt 包在处理 error 时会调用 Error 方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
package main
import "fmt"
// 定义一个 DivideError 结构体,用于表示除法错误
type DivideError struct {
dividee int // 被除数
divider int // 除数
}
// 实现 `error` 接口的 Error 方法
func (de *DivideError) Error() string {
// 定义错误信息的格式
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
// 返回格式化的错误信息
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
// 检查除数是否为零
if varDivider == 0 {
// 创建 DivideError 实例并填充数据
dData := DivideError{
dividee: varDividee,
divider: varDivider, // 除数为零
}
// 调用 Error 方法获取错误信息
errorMsg = dData.Error()
return // 返回结果为 0,错误信息
} else {
// 正常情况下返回计算结果和空错误信息
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况:进行除法运算
if result, errorMsg := Divide(100, 10); errorMsg == "" {
// 如果没有错误,则打印结果
fmt.Println("100/10 = ", result)
}
// 当除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
// 如果有错误,则打印错误信息
fmt.Println("errorMsg is: ", errorMsg)
}
}
// 输出结果
100/10 = 10
errorMsg is:
Cannot proceed, the divider is zero.
dividee: 100
divider: 0
异常处理
在 Go 语言中,panic
是一种用于处理程序中意外情况的机制。当程序遇到严重错误或无法继续执行的情况时,可以使用 panic
来引发一个错误。引发 panic
会导致程序的正常执行流程中断,并开始执行延迟(defer)函数,最终导致程序崩溃。
Golang 没有结构化异常,使用 panic 抛出错误,recover 捕获错误。
异常的使用场景简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理。
使用场景
panic
通常用于以下情况:
- 不可恢复的错误:当程序遇到无法处理的错误,例如数组越界、空指针解引用等。
- 程序内部错误:例如,逻辑错误或不应该发生的情况。
基本用法
你可以使用内置的 panic
函数来引发一个 panic
,例如:
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from:", r)
}
}()
fmt.Println("Starting program...")
panic("Something went wrong!")
fmt.Println("This line will not be executed.")
}
- defer 和 recover:在上面的例子中,使用
defer
关键字定义了一个恢复函数。当panic
发生时,recover
函数可以捕获到这个panic
并防止程序直接崩溃。 - panic:调用
panic
会引发一个运行时错误,程序的执行会立即停止,直到找到一个recover
来处理它。 - 程序崩溃:如果没有使用
recover
,程序会打印出错误信息并退出。
注意事项
- 利用
recover
处理panic
指令,defer
必须放在panic
之前定义,另外recover
只有在defer
调用的函数中才有效。否则当panic
时,recover
无法捕获到panic
,无法防止panic
扩散。 recover
处理异常后,逻辑并不会恢复到panic
那个点去,函数跑到defer
之后的那个点。- 多个
defer
会形成defer
栈,后定义的defer
语句会被最先调用。
在 Go 语言中,panic
通常会在以下几种情况下引发错误:
- 数组越界
当尝试访问数组或切片中不存在的索引时,会引发 panic
。
arr := []int{1, 2, 3}
fmt.Println(arr[3]) // 引发 panic: index out of range
- 空指针解引用
尝试解引用一个为 nil
的指针时,会引发 panic
。
var p *int
fmt.Println(*p) // 引发 panic: dereference of nil pointer
- 类型断言失败
在进行类型断言时,如果断言失败,会引发 panic
。
var i interface{} = "hello"
s := i.(int) // 引发 panic: interface conversion: interface {} is string, not int
- 使用
panic
函数
显式调用 panic
函数时,会引发错误。
panic("something went wrong") // 引发 panic
- 运行时错误
某些运行时错误,例如死循环或堆栈溢出,也可能导致 panic
。
- 其他严重错误
在程序中遇到不应该发生的情况,例如逻辑错误、非法状态等,也可以使用 panic
手动引发。
示例代码
以下是一个简单的示例,展示了几种引发 panic
的情况:
package main
import "fmt"
func main() {
// 数组越界
arr := []int{1, 2, 3}
fmt.Println(arr[3]) // panic: index out of range
// 空指针解引用
var p *int
fmt.Println(*p) // panic: dereference of nil pointer
// 类型断言失败
var i interface{} = "hello"
s := i.(int) // panic: interface conversion: interface {} is string, not int
}
可以使用 defer
和 recover
来捕获 panic
,防止程序崩溃。
func safeFunc() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from:", r)
}
}()
// 可能引发 panic 的代码
arr := []int{1, 2, 3}
fmt.Println(arr[3]) // 引发 panic
}
func main() {
safeFunc()
fmt.Println("Program continues...")
}
在这个示例中,recover
捕获了 panic
,程序不会崩溃,而是继续执行后面的代码。
其他知识点
panic
的工作机制
- 引发
panic
:当panic
被调用时,Go 运行时系统会开始展开当前调用栈的函数。所有的defer
语句都会被执行。 - 展开调用栈:在
panic
发生后,程序会向上返回,执行每个函数的defer
,直到找到一个recover
或完全退出程序。 - 最终崩溃:如果没有
recover
捕获到panic
,程序将打印出错误信息并退出,返回状态码1。
- 使用
defer
和recover
defer
声明:你可以在函数中使用defer
关键字来声明一个延迟执行的函数。这个函数会在返回之前执行,即使函数中发生了panic
。recover
函数:recover
可以用来捕获panic
并阻止程序崩溃。它只在被defer
声明的函数中有效。
panic
的参数
panic
可以接受任意类型的参数,通常是一个字符串或一个错误对象。这个参数会被打印出来,提供有关panic
的上下文信息。
panic("an error occurred")
- 何时使用
panic
- 程序内部错误:如果遇到不应该发生的逻辑错误,可以使用
panic
,例如检查到不符合预期的状态。 - 初始化错误:在初始化过程中,如果遇到无法恢复的错误,可以使用
panic
。 - 开发阶段:在开发期间,
panic
可以帮助快速发现问题,但在生产代码中应谨慎使用。
panic
与错误处理的对比
- 错误处理:Go 语言推荐使用返回值来处理可恢复的错误,使用
error
类型来传递错误信息。 panic
:用于处理不可恢复的错误,通常意味着程序处于不一致状态。
- 性能影响
- 使用
panic
和recover
会对性能有一定影响,尤其是在高频调用的情况下。频繁的panic
和recover
会增加运行时开销。
- 其他注意事项
- 嵌套
panic
:如果在defer
的恢复函数中再次调用panic
,将导致程序再次崩溃,而不会执行外层的recover
。 - 并发:在并发程序中,
panic
只会影响当前 goroutine。如果一个 goroutine 发生了panic
,其他 goroutine 不会受到影响。
参考课程: