错误+延迟执行+panic&recover+单步调试
1 error 接口
Go 语言通过 error 接口实现错误处理的标准模式,通过使用函数返回值列表中的最后一个值返回错误信息,将错误的处理交由程序员主动进行处理
在程序中运行时错误错误一般分为两种:
- 可恢复的错误(重试/或忽略)
- 不可恢复的错误(程序退出)
范例代码:
package main
import (
"fmt"
"strconv"
)
func main() {
// 需要给调用者返回错误信息
// 通过函数最后一个返回值返回错误!
// 调用者需要对错误进行检查,决定如何操作
v, err := strconv.Atoi("123")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(v)
}
}
[14:35:31 root@go day1]#go run main.go
123
1.2 error 接口初始化方法
下面两种效果其实是一样的,并没有区别
1.2.1 第一种初始化 error 方式
通过 fmt.Errorf 方法创建
定义函数实现 error 范例:
package main
import (
"fmt"
)
// error 接口类型
func div(left, right int) (int, error) {
if right == 0 {
// 在 go 中通过, fmt.Errorf 就看返回错误
return 0, fmt.Errorf("right num is zeror")
}
return left / right, nil
}
func main() {
res, err := div(10, 0)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res)
}
}
[14:38:05 root@go day1]#go run main.go
right num is zeror
1.2.2 第二种初始化 error 方法
通过 errors 包的 New 方法创建
范例代码:
package main
import (
"errors"
"fmt"
)
// error 接口类型
func div(left, right int) (int, error) {
if right == 0 {
// 在 go 中通过, errors.New 即可返回错误
return 0, errors.New("参数传递错误")
}
return left / right, nil
}
func main() {
res, err := div(10, 0)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res)
}
}
[14:50:42 root@go day1]#go run main.go
参数传递错误
1.3 对 error 错误进行处理
看下面代码,如果说传递个 div 的参数是错误的话,我们可以输出提示错误信息,从而后续可以对代码进行调试
package main
import (
"errors"
"fmt"
)
// error 接口类型
func div(left, right int) (int, error) {
if right == 0 {
return 0, errors.New("right num is 0")
}
return left / right, nil
}
func main() {
if res, err := div(10, 0); err != nil {
fmt.Println("value is error :", err)
} else {
fmt.Println(res)
}
}
[14:52:25 root@go day1]#go run main.go
value is error : right num is 0
2 defer 延迟执行-压栈
defer 关键字用户声明函数,不论函数是否发生错误都在函数执行最后执行(return 之前),若使用 defer 声明多个函数,则按照声明的顺序,先声明后执行(堆)常用来做资源释放,记录日志关闭文件以及关闭链接等工作
我们可以看到通过下面代码,defer 的执行顺序是最后执行,也就是说当函数存在其他代码块的时候,优先执行其他代码块,最后在执行 defer ,而且当有多个 defer 的时候是先进后出
package main
import "fmt"
func main() {
fmt.Println("start")
defer func() {
fmt.Println("defer")
}()
defer func() {
fmt.Println("12")
}()
fmt.Println("END")
}
[14:57:43 root@go day1]#go run main.go
start
END
12
defer
通过输出,我们会还想 fmt.Println("defer")
是先进,但是后出
所以 defer 的执行顺序是先进后出,谁先声明谁后执行
2.1 defer 细节1
我们可以通过下面代码发现, defer 并不受循环影响。
package main
import "fmt"
func main() {
for i := 0; i < 2; i++ {
fmt.Println("start")
defer func() {
// 这里的 i 寻找的是 for 循环的 i
fmt.Printf("defer %d\n", i)
}()
fmt.Println("stop")
}
}
[15:00:21 root@go day1]#go run main.go
start
stop
start
stop
defer 2
defer 2
输出结果看到,defer 2 ,为什么会是 2 呢,因为 defer 在使用的时候其实使用了外部的变量,所以最后 i 成 2 了,由于 for 循环完毕执行了 i++,而且在这个代码中只有一个匿名函数,我在上面说过 defer 最后执行,所以这里 defer 自然就被压到了最后执行,通过输出就是两次 defer 2
在最后,因为这里在 for 循环中 defer 循环了两次
可以通过下面代码解决上面问题
每次执行完了之后我们将 i 传递到 defer func()
中去,因为这里将 i 变量传递到了 defer 匿名函数中,所以这里的 i 不再是最后的那个 i++
package main
import "fmt"
func main() {
for i := 0; i < 2; i++ {
fmt.Println("start")
defer func(i int) {
fmt.Printf("defer %d\n", i)
}(i)
fmt.Println("stop")
}
}
[15:06:01 root@go day1]#go run main.go
start
stop
start
stop
defer 1
defer 0
延迟执行或者闭包的时候,参数传递的时候一定要注意
2.2 defer 细节2
在延迟执行中尽量不要修改返回值,因为 defer 开启匿名函数然后我们的值就会在匿名函数中被修改
package main
import "fmt"
func test() (i int) {
// 在延迟执行中尽量不要修改返回值
i = 1
defer func() {
fmt.Println("defer")
// 在 defer 中将 i 修改为 2
i = 2
}()
return i
}
func main() {
fmt.Println(test())
}
[15:07:42 root@go day1]#go run main.go
defer
2
3 panic & recover
go 语言提供 panic 和 recover 函数用于处理运行时错误,当调用 panic 抛出错误,中断原有的控制流程,常用于不可修复性错误。recover 函数用于终止错误处理流程,仅在 defer语句的函数中有效,用于截取错误处理流程,recover 只能捕获到最后一个错误
初识 panic
panic 其实是在程序员执行程序的时候用来抛出错误
package main
func main() {
panic("我是错误")
}
[15:10:12 root@go day1]#go run main.go
panic: 我是错误
goroutine 1 [running]:
main.main()
/root/project/2022/day1/main.go:4 +0x39
exit status 2
当出现错误,一般要用延迟执行加 recover
package main
import "fmt"
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("出错了,进行恢复", err)
}
}()
panic("我是错误")
}
[15:12:30 root@go day1]#go run main.go
出错了,进行恢复 我是错误
假如我们把 panic 注释掉就不抛出错误,因为 recover 只能够捕获 panic
package main
import "fmt"
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("出错了,进行恢复", err)
}
}()
// panic("我是错误")
}
通过函数调用实现控制 recover 和 panic
package main
import "fmt"
func test(b bool) {
defer func() {
if err := recover(); err != nil {
fmt.Println("出现错误:", err)
}
}()
if b {
fmt.Println("允许...")
} else {
panic("panic err")
}
}
func main() {
test(true) // b 为 true 就执行允许
test(false) // b 为 false 执行 panic err
}
可以通过这种方式来控制程序的执行逻辑
[23:47:31 root@go day1]#go run main.go
允许...
出现错误: panic err
recover 还能够捕获其他函数中的 painc
package main
import "fmt"
func callback(b bool) {
// 如果 b 为 true 就执行 running,否则就是 panic 报错
if b {
fmt.Println("running...")
} else {
fmt.Println("callback panic")
}
}
func test(b bool) {
// defer 在函数中最后执行, 所以这里会先执行 callback 函数用来判断 b 是否为 true 或者 false
defer func() {
if err := recover(); err != nil {
fmt.Println("recover err", err)
}
}()
// 调用 callback 函数
callback(b)
}
func main() {
test(true)
test(false)
}
通过输出发现 recover 也能捕获到其他函数的错误
[23:50:53 root@go day1]#go run main.go
running...
callback panic
3.1 recover 的处理
recover 的处理,一般会用 recover 捕获到错误,然后进行返回
package main
import "fmt"
func callback(b bool) {
if b {
panic("callback err panic") // b 为 true 返回 panic
}
fmt.Println("running")
}
// 定义返回值为 error 类型
func test(b bool) (err error) {
defer func() {
if msg := recover(); msg != nil { // 定义 msg 接收 recover 的返回值,捕获panic
err = fmt.Errorf("%s", msg) // 通过 fmt.Errorf 接受到有 callback 传递的 panic 错误值并传递给 err
}
}()
callback(b)
// 返回命名返回值,可以看到 return 的时候其实是一个空因为他会自动返回 recover 在 callback 函数中捕获到 panic 的值
return
}
func main() {
fmt.Println(test(true))
fmt.Println(test(false))
fmt.Println(test(false))
}
[16:17:23 root@go day1]#go run main.go
callback err panic
running
<nil>
running
<nil>