2026-01-25 16:38:17 +08:00
|
|
|
|
# 异常处理
|
|
|
|
|
|
|
|
|
|
|
|
程序运行过程中,可能会有异常,比如: 输入参数错误,文件不存在,数据库连接失败等等
|
|
|
|
|
|
|
2026-01-25 18:08:25 +08:00
|
|
|
|
[defer与异常课件](https://gitee.com/infraboard/go-course/blob/master/zh-cn/base/error.md#defer%E7%9A%84%E5%BA%94%E7%94%A8)
|
|
|
|
|
|
|
2026-01-25 16:38:17 +08:00
|
|
|
|
## error (程序异常)
|
|
|
|
|
|
|
|
|
|
|
|
### 处理 error
|
|
|
|
|
|
|
|
|
|
|
|
异常处理的流派:
|
|
|
|
|
|
+ 通过返回值值处理: resp, error := func() (resp, error)
|
|
|
|
|
|
+ try catch: try { ... } catch(e) { ... }, 面向对象的语言: java, python ,js,
|
|
|
|
|
|
|
|
|
|
|
|
通过一个返回值来处理异常
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
resp, err := func() (resp, error)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
fmt.Println("Error:", err)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
// 变量目录, 打印了文件名称
|
|
|
|
|
|
err := walkDir("./xxxx", func(filePath string) {
|
|
|
|
|
|
fmt.Println(filePath)
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
fmt.Println("Error:", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 最常见的就是 遍历目录里面的文件
|
|
|
|
|
|
func walkDir(path string, fn func(string)) error {
|
|
|
|
|
|
files, err := os.ReadDir(path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取path目录下的文件和子目录
|
|
|
|
|
|
for _, file := range files {
|
|
|
|
|
|
// 如果是目录, 继续往下找
|
|
|
|
|
|
if file.IsDir() {
|
|
|
|
|
|
walkDir(path+"/"+file.Name(), fn)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 文件, 调用fn函数处理
|
|
|
|
|
|
fn(path + "/" + file.Name())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 怎么抛出error
|
|
|
|
|
|
|
|
|
|
|
|
1. 直接返回 底层的error到上层 (return error)
|
|
|
|
|
|
```go
|
|
|
|
|
|
return err
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. 通过error包来New一个error (errors.New)
|
|
|
|
|
|
```go
|
|
|
|
|
|
files, err := os.ReadDir(path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New(fmt.Sprintf("File Read Error: %s", err))
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. 直接封装下,封装成自己的error(fmt.Errorf)
|
|
|
|
|
|
```go
|
|
|
|
|
|
files, err := os.ReadDir(path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("File Read Error: %s", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. 组合异常,再底层的异常上,添加自己定义的异常(errors.Join)
|
|
|
|
|
|
```go
|
|
|
|
|
|
files, err := os.ReadDir(path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.Join(errors.New("walkDir error"), err)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 异常的比较 (使用 errors.Is)
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
var (
|
|
|
|
|
|
ErrInvalid = errors.New("invalid argument")
|
|
|
|
|
|
ErrPermission = errors.New("permission denied")
|
|
|
|
|
|
ErrExist = errors.New("file already exists")
|
|
|
|
|
|
ErrNotExist = errors.New("file does not exist")
|
|
|
|
|
|
ErrClosed = errors.New("file already closed")
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
|
|
fmt.Println(ErrFileNotFound)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fmt.Printf("%s", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## panic (程序崩溃)
|
|
|
|
|
|
|
|
|
|
|
|
### pannic
|
|
|
|
|
|
|
|
|
|
|
|
非常危险,空指针(NPE):
|
|
|
|
|
|
|
|
|
|
|
|
windows蓝屏这种, 特别危险的操作, 如果继续运行可能会造成安全隐患, 内存的非法访问, A程序的内存(内存地址A, 用完后把内存地址释放, OS 这一个物理内存 分配给了程序B, A再次访问已经释放的内存地址A), 崩溃的其中一个场景,内存非发访问
|
|
|
|
|
|
|
|
|
|
|
|
go 是可以 绕开指针类型系统(nil), 直接使用unsafe包,访问地址地址
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// pannic
|
|
|
|
|
|
// 索引4 就是非法内存
|
|
|
|
|
|
arrs := []int{1, 2, 3}
|
|
|
|
|
|
fmt.Println(arrs[4])
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
panic: runtime error: index out of range [4] with length 3
|
|
|
|
|
|
|
|
|
|
|
|
goroutine 1 [running]:
|
|
|
|
|
|
main.main()
|
|
|
|
|
|
/Users/yumaojun/Projects/go-course/go20/day03/error/main.go:28 +0x44
|
|
|
|
|
|
exit status 2
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### recover: 从崩溃中恢复
|
|
|
|
|
|
|
|
|
|
|
|
你写一个API Server, 不小心 panic: runtime error: index out of range [4] with length 3, 程序崩溃,无法继续处理用户请求,你网站有1000个接口,产生这个报错的是其中一个接口的一个流程,需要阻止程序崩溃,打印日志就行
|
|
|
|
|
|
|
|
|
|
|
|
捕获panic信号, 捕获是一个特殊逻辑,程序退出前 去检查有没有panic信号的, 放执行前面和后面都不行,需要放到 函数调用结束后执行(Hook)
|
|
|
|
|
|
```go
|
|
|
|
|
|
func recoverHandler() {
|
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
|
fmt.Println("Recovered from panic:", r)
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
defer recoverHandler()
|
|
|
|
|
|
...
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## defer (函数Hook)
|
|
|
|
|
|
|
|
|
|
|
|
函数执行完成后,调用你的defer 函数进行 资源清理或者崩溃恢复
|
|
|
|
|
|
|
2026-01-25 17:13:22 +08:00
|
|
|
|
一个 流程(协程)里面,可以有个defer语句,声明 函数执行完成后的Hook调用
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
defer panicHandler()
|
|
|
|
|
|
|
|
|
|
|
|
// if r := recover(); r != nil {
|
|
|
|
|
|
// fmt.Println("Recovered from panic:", r)
|
|
|
|
|
|
// os.Exit(1)
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// 变量目录, 打印了文件名称
|
|
|
|
|
|
err := walkDir("./xxxx", func(filePath string) {
|
|
|
|
|
|
fmt.Println(filePath)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
|
|
fmt.Println("not exist", err)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fmt.Printf("%s", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化数据库,连接数据库
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
fmt.Println("关闭数据库连接")
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// pannic
|
|
|
|
|
|
// arrs := []int{1, 2, 3}
|
|
|
|
|
|
// fmt.Println(arrs[4])
|
|
|
|
|
|
|
|
|
|
|
|
// if r := recover(); r != nil {
|
|
|
|
|
|
// fmt.Println("Recovered from panic:", r)
|
|
|
|
|
|
// os.Exit(1)
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// 启动API服务器 HTTP Server
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
fmt.Println("关闭HTTP服务器")
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// not exist walkDir error
|
|
|
|
|
|
// open ./xxxx: no such file or directory
|
|
|
|
|
|
// 关闭HTTP服务器
|
|
|
|
|
|
// 关闭数据库连接
|
|
|
|
|
|
// panic handler
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-25 16:38:17 +08:00
|
|
|
|
|