# 异常处理

程序运行过程中，可能会有异常，比如: 输入参数错误，文件不存在，数据库连接失败等等

[defer与异常课件](https://gitee.com/infraboard/go-course/blob/master/zh-cn/base/error.md#defer%E7%9A%84%E5%BA%94%E7%94%A8)

## 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
```


