go20/day03/error/README.md
yumaojun03 2e443239a1 ```
docs(error): 更新defer机制文档并添加实际代码示例

- 修改panicHandler函数名称替换recoverHandler
- 在README.md中添加完整的defer执行流程代码示例
- 展示数据库连接和HTTP服务器关闭的defer应用场景
- 更新drawio图表展示defer hook调用机制和执行顺序
- 添加多个defer语句的实际应用案例
```
2026-01-25 17:13:22 +08:00

212 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 异常处理
程序运行过程中,可能会有异常,比如: 输入参数错误,文件不存在,数据库连接失败等等
## 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 函数进行 资源清理或者崩溃恢复
一个 流程(协程)里面可以有个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
```