|
|
@@ -0,0 +1,161 @@
|
|
|
+# 第 11 课:error 错误处理
|
|
|
+
|
|
|
+## 学习目标
|
|
|
+
|
|
|
+之前用 bool 表达成功或失败,现在让函数返回失败原因。学会 `(结果, error)`、`if err != nil`、提前返回,以及向调用方传递错误。
|
|
|
+
|
|
|
+按 [main.go](./main.go) 逐段手敲。本课只使用标准库 errors、fmt、strconv,无需安装依赖。暂不引入自定义错误类型、错误包装、panic / recover 或匿名函数。
|
|
|
+
|
|
|
+## 1. 成功返回 nil,失败返回 error
|
|
|
+
|
|
|
+```go
|
|
|
+func divide(a, b int) (float64, error) {
|
|
|
+ if b == 0 {
|
|
|
+ return 0, errors.New("除数不能为 0")
|
|
|
+ }
|
|
|
+ return float64(a) / float64(b), nil
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+逐项理解:
|
|
|
+
|
|
|
+- `(float64, error)`:两个返回值,第一个是结果,第二个是错误。
|
|
|
+- `error`:Go 预定义的接口类型,现在先理解成“保存错误信息的类型”,接口细节后续学习。
|
|
|
+- `errors.New(...)`:根据一段文字创建错误值,不会自动打印,也不会自动终止程序。
|
|
|
+- `nil`:在这里表示没有错误,不是字符串 `"nil"`。
|
|
|
+- 失败时返回的 0 是占位结果,本函数约定出现错误时不要使用它。
|
|
|
+
|
|
|
+error 不是关键字,也不是异常抛出语法。Go 的这种常规错误处理依靠返回值和显式判断。
|
|
|
+
|
|
|
+## 2. 调用后先检查错误
|
|
|
+
|
|
|
+```go
|
|
|
+result, err := divide(10, 4)
|
|
|
+if err != nil {
|
|
|
+ fmt.Println("计算失败:", err)
|
|
|
+} else {
|
|
|
+ fmt.Println("计算结果:", result)
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+err 是普通变量名,习惯用它保存错误。`err != nil` 表示发生了错误;`err == nil` 表示本次调用成功。
|
|
|
+
|
|
|
+不要通过结果是不是 0 判断成功:0 本身可能是合法结果。例如本课 `parseScore("0")` 返回 `(0, nil)`。
|
|
|
+
|
|
|
+本课为方便观察,在 main 中既展示成功也展示失败。实际辅助函数中常写成:
|
|
|
+
|
|
|
+```go
|
|
|
+result, err := divide(10, 4)
|
|
|
+if err != nil {
|
|
|
+ return err // 这段需要放在返回 error 的函数中。
|
|
|
+}
|
|
|
+// 下面只处理成功路径,使用 result。
|
|
|
+```
|
|
|
+
|
|
|
+这个 return 只退出当前函数,不自动退出整个程序。返回类型必须匹配,不能在无返回值的 main 中直接写 `return err`。
|
|
|
+
|
|
|
+本课失败时约定忽略结果;标准库中某些函数会同时返回部分结果和错误,应以具体文档约定为准。
|
|
|
+
|
|
|
+## 3. 只有 error 的函数
|
|
|
+
|
|
|
+```go
|
|
|
+func validateScore(score int) error {
|
|
|
+ if score < 0 || score > 100 {
|
|
|
+ return fmt.Errorf("成绩 %d 无效,应在 0~100 之间", score)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+有些操作没有额外结果,只有“成功或失败”,就只返回 error。
|
|
|
+
|
|
|
+`fmt.Errorf` 创建带格式的错误信息,支持 `%d`、`%s` 等占位符;它与 `fmt.Printf` 不同,**不会直接输出到终端**。
|
|
|
+
|
|
|
+## 4. 调用标准库并传递错误
|
|
|
+
|
|
|
+```go
|
|
|
+score, err := strconv.Atoi(text)
|
|
|
+if err != nil {
|
|
|
+ return 0, err
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+`strconv.Atoi` 将十进制字符串转换为 int;例如 `"85"` 成功,`"abc"` 失败。超出 int 可表示范围也会失败。它不会自动去掉字符串两端的空白。
|
|
|
+
|
|
|
+parseScore 的执行顺序:
|
|
|
+
|
|
|
+1. 把文本转成整数,转换失败立即返回错误。
|
|
|
+2. 校验整数是否在 0~100 之间,校验失败立即返回错误。
|
|
|
+3. 两步都通过,返回整数和 nil。
|
|
|
+
|
|
|
+函数内部负责计算和返回错误,main 负责决定怎样显示、继续还是停止,避免同一个错误在每一层重复打印。
|
|
|
+
|
|
|
+源码中第二次给 err 赋值使用 `err = validateScore(score)`,因为 err 已存在,不需要再次用 `:=`。
|
|
|
+
|
|
|
+## 5. 错误不等于 panic
|
|
|
+
|
|
|
+普通 error 是可供调用方检查的返回值,适合无效输入、找不到文件、网络失败等情况。只要调用方处理得当,程序可以继续执行。
|
|
|
+
|
|
|
+panic 是另一种运行时机制,例如解引用 nil 指针可能引发 panic;它不是 error 返回值,也不会自动被 `if err != nil` 捕获。本课不使用 panic 处理普通输入错误。
|
|
|
+
|
|
|
+不要为了省事写 `score, _ := strconv.Atoi(text)` 忽略错误,否则转换失败可能被误当作合法的 0。以后学习错误分类时,也不要仅凭错误文字来判断错误种类。
|
|
|
+
|
|
|
+## 编译并运行
|
|
|
+
|
|
|
+在**仓库根目录**执行:
|
|
|
+
|
|
|
+```powershell
|
|
|
+New-Item -ItemType Directory -Force bin/11_errors | Out-Null
|
|
|
+go build -o ./bin/11_errors/errors.exe ./lessons/11_errors
|
|
|
+.\bin\11_errors\errors.exe
|
|
|
+```
|
|
|
+
|
|
|
+修改源码后需要重新编译。快速编译并运行:
|
|
|
+
|
|
|
+```powershell
|
|
|
+go run ./lessons/11_errors
|
|
|
+```
|
|
|
+
|
|
|
+或从根目录进入本课:
|
|
|
+
|
|
|
+```powershell
|
|
|
+Set-Location lessons/11_errors
|
|
|
+go run .
|
|
|
+```
|
|
|
+
|
|
|
+## 预期输出
|
|
|
+
|
|
|
+```text
|
|
|
+=== 成功返回 nil ===
|
|
|
+10 / 4 = 2.50
|
|
|
+
|
|
|
+=== 失败返回原因 ===
|
|
|
+计算失败: 除数不能为 0
|
|
|
+
|
|
|
+=== 只有 error 返回值 ===
|
|
|
+校验失败: 成绩 120 无效,应在 0~100 之间
|
|
|
+
|
|
|
+=== 字符串转换与错误传递 ===
|
|
|
+输入 "85" 成功:成绩=85
|
|
|
+输入 "0" 成功:成绩=0
|
|
|
+输入 "120" 失败:成绩 120 无效,应在 0~100 之间
|
|
|
+输入 "abc" 失败:strconv.Atoi: parsing "abc": invalid syntax
|
|
|
+处理结束:错误已由调用方处理,程序继续运行
|
|
|
+```
|
|
|
+
|
|
|
+标准库错误文字可能随版本变化,理解含义即可,不要把完整字符串当作稳定接口。
|
|
|
+
|
|
|
+## 动手练习
|
|
|
+
|
|
|
+- [ ] 把除数改成 2、-2、0,观察成功和失败分支。
|
|
|
+- [ ] 添加输入 `"100"`、`"-1"`、`"101"`、`"80.5"`,先预测再运行。
|
|
|
+- [ ] 试一下 `" 85 "`,确认空白不会被 Atoi 自动忽略。
|
|
|
+- [ ] 修改校验错误信息,观察 fmt.Errorf 返回的错误在 main 中如何显示。
|
|
|
+- [ ] 新写 `checkAge(age int) error`,为自己约定的合法年龄范围返回 nil 或错误。
|
|
|
+- [ ] 回看上一课 SetScore 的 bool 返回值,思考改成 error 后怎样报告具体原因。
|
|
|
+
|
|
|
+若另行手敲,放到本课的 `practice/` 子目录,用 `go run ./lessons/11_errors/practice` 运行,不要在同一个包中重复定义 main 或同名函数。
|
|
|
+
|
|
|
+下一课学习 switch 与 defer,分别处理多分支选择和函数退出前的操作。
|
|
|
+
|
|
|
+[上一课:方法与接收者](../10_methods/README.md) · [学习总览](../../README.md)
|