# 异常处理 程序运行过程中,可能会有异常,比如: 输入参数错误,文件不存在,数据库连接失败等等 ## 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 ```