深入理解与使用go之错误处理–实现
引子
错误管理是构建健壮和可观察的应用程序的一个基本方面,它应该与代码库的任何其他部分一样重要。在Go中,错误管理不像大多数编程语言那样依赖于传统的try/catch
机制。相反,错误作为正常返回值返回。那么问题来了:
- 程序什么时候发生崩溃(panic)
- 通常处理错误的方式是怎样的
- 我们应该忽略错误么
- 在defer里出错应该怎么处理呢
- 所有的panic错误都是可以捕获的么
我记得刚写程序的时候,感觉对go的错误很茫然,一股脑的忽略错误,像下面这样
// json 解码
_ = json.Unmarshal(data, &User)
// 打开文件
f, _ := os.Open("hello")
或者,我们避无可避,选择这样判断错误
if strings.Contains(err.Error(), "op error") {
return ""
}
return data
- 如果json解码的是配置文件,忽略错误会直接导致整个程序启动崩溃
- 打开文件如果没有权限,下面的所有操作直接panic
- 如果A开发错误内容是小写 “op error” 而B开发是大写 “OP ERROR”, 能判断么
带着上面的这些问题,我们来讨论讨论今天要说的错误处理
错误处理
错误的分类
我觉得开始处理之前,我们很有必要对错误进行一定的分类
- 崩溃型错误
- 系统调用出现的崩溃 如堆栈溢出、数组越界、空指针引用等等
- 程序限制型崩溃,如初始化过程中的配置读取、日志路径权限等等,出现错误整个程序就不该继续往下走
- 普通型错误
- 错误我们需要处理,比如数据库连接异常 我们进行重试
- 正常型错误
- 数据库因为查询为空返回的错误
- 文件读取到末尾的EOF错误
- 我们不感兴趣且对程序执行不会产生重大影响的错误
有了这三个分类,我们来一个一个看
崩溃型错误
系统调用崩溃性错误
这种错误一般是程序为了避免进一步的不确定行为和数据损坏,而提前退出
如 数组越界
a := []int{ 1, 2, 3} fmt.Println(a[3])
运行会直接panic
panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.main() /data/www/hello/main.go:12 +0x1b exit status 2
程序限制型
net/http
包的server.go
有段代码func checkWriteHeaderCode(code int) { if code < 100 || code > 999 { panic(fmt.Sprintf("invalid WriteHeader code %v", code)) } }
原话是:
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's // no equivalent bogus thing we can realistically send in HTTP/2, // so we'll consistently panic instead and help people find their bugs // early. (We can't return an error from WriteHeader even if we wanted to.) 我们过去常常在网络上发送“HTTP/1.1 000 0”作为响应,但现在。 我们不能在HTTP/2中实际发送等同的虚假信息, 因此,我们将持续恐慌,帮助人们及早发现他们的缺陷。(即使我们想从WriteHeader返回错误,也不能这样做。)
database/sql
包sql.go
func Register(name string, driver driver.Driver) { driversMu.Lock() defer driversMu.Unlock() if driver == nil { panic("sql: Register driver is nil") } if _, dup := drivers[name]