go20/day03/error/README.md
yumaojun03 bb8564ba49 ```
feat(error): 添加Go语言异常处理完整教程

详细介绍了Go语言中的错误处理机制,包括:

- error类型的处理方式,通过返回值处理异常
- panic和recover的使用方法,以及如何从程序崩溃中恢复
- defer函数的应用,用于资源清理和崩溃恢复
- 错误创建的不同方式:errors.New、fmt.Errorf、errors.Join
- 错误比较的最佳实践:使用errors.Is进行错误判断
- 实际代码示例展示了目录遍历中的错误处理
- 添加了协程调度的架构图解
```
2026-01-25 16:38:17 +08:00

160 lines
3.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 函数进行 资源清理或者崩溃恢复