Go语言-错误与异常
第 18 章 错误与异常
18.1 错误
18.1.1 内建错误
在 Go 中, 错误 使用内建的 error
类型表示。error
类型是一个接口类型,它的定义如下:
1 | type error interface { |
error
有了一个签名为 Error() string
的方法。所有实现该接口的类型都可以当作一个错误类型。Error()
方法给出了错误的描述。fmt.Println
在打印错误时,会在内部调用 Error() string
方法来得到该错误的描述。
下面的例子演示了程序尝试打开一个不存在的文件导致的报错:
1 | package main |
我们这里没有存在一个文件 a.txt
,所以尝试打开文件将会返回一个不等于 nil
的错误。
1 | open /a.txt: The system cannot find the file specified. |
18.1.2 自定义错误
使用 errors
包中的 New
函数可以创建自定义错误。下面是 errors
包中 New
函数的实现代码:
1 | package errors |
errorString
是一个结构体类型,只有一个字符串字段 s
。它使用了 errorString
指针接受者,来实现 error
接口的 Error() string
方法。New
函数有一个字符串参数,通过这个参数创建了 errorString
类型的变量,并返回了它的地址。于是它就创建并返回了一个新的错误。
下面是一个简单的自定义错误例子,该例子创建了一个计算矩形面积的函数,当矩形的长和宽两者有一个为负数时,就会返回一个错误:
1 | package main |
运行上面的程序会报出自定义的错误:
1 | 计算错误, 长度或宽度,不能小于0. |
18.1.3 给错误添加更多信息
上面的程序能报出我们自定义的错误,但是没有具体说明是哪个数据出了问题,所以下面就来改进一下这个程序,我们使用 fmt
包中的 Errorf
函数,规定错误格式,并返回一个符合该错误的字符串。
1 | package main |
运行上面的程序,我们可以看到输出的错误中打印了长度和宽度的具体值:
1 | 计算错误, 长度100或宽度-10,不能小于0 |
当然,给错误添加更多信息还可以 使用结构体类型和字段 实现。下面还是通过改进上面的程序来讲解这种方法的实现:
首先创建一个表示错误的结构体类型,一般错误类型名称都是以 Error
结尾,上面的错误是由于面积计算中长度或宽度错误导致的,所以这里把结构体命名为 areaError
:
1 | package main |
运行该程序输出如下:
1 | length 100 or width -10 is less than zero |
当然,我们还可以使用 结构体类型的方法 来给错误添加更多信息。下面我们继续完善上面的程序,让程序更加精确的定位是长度引发的错误还是宽度引发的错误。
首先,我们还是跟上面一样创建一个表示错误的结构体:
1 | package main |
还是使用之前的例子中的参数,但我们这次报错结果更加具体,运行该程序输出如下:
1 | error: width -10 is less than zero |
18.2 异常
错误和异常是两个不同的概念,非常容易混淆。错误指的是可能出现问题的地方出现了问题;而异常指的是不应该出现问题的地方出现了问题。
18.2.1 panic
在有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 panic
来终止程序。当函数发生 panic
时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic
信息,接着打印出堆栈跟踪,最后程序终止。
我们应该尽可能地使用错误,而不是使用 panic
和 recover
。只有当程序不能继续运行的时候,才应该使用 panic
和 recover
机制。
panic
有两个合理的用例:
- 发生了一个不能恢复的错误,此时程序不能继续运行。一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用
panic
,因为如果不能绑定端口,啥也做不了。 - 发生了一个编程上的错误。假如我们有一个接收指针参数的方法,而其他人使用
nil
作为参数调用了它。在这种情况下,我们可以使用panic
,因为这是一个编程错误:用nil
参数调用了一个只能接收合法指针的方法。
18.2.2 触发 panic
下面是内建函数 panic
的签名:
1 | func panic(v interface{}) |
当程序终止时,会打印传入 panic
的参数。
1 | package main |
运行上面的程序,会打印出传入 panic
函数的信息,并打印出堆栈跟踪:
1 | panic: panic error |
18.2.3 发生 panic 时的 defer
上面已经提到了,当函数发生 panic
时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic
信息,接着打印出堆栈跟踪,最后程序终止。下面通过一个简单的例子看看是不是这样:
1 | package main |
运行该程序后输出如下:
1 | defer myTest |
18.2.4 recover
recover
是一个内建函数,用于重新获得 panic
协程的控制。下面是内建函数 recover
的签名:
1 | func recover() interface{} |
recover
必须在 defer
函数中才能生效,在其他作用域下,它是不工作的。在延迟函数内调用 recover
,可以取到 panic
的错误信息,并且停止 panic
续发事件,程序运行恢复正常。下面是网上找的一个例子:
1 | package main |
虽然该程序触发了 panic
,但由于我们使用了 recover()
捕获了 panic
异常,并输出 panic
信息,即使 panic
会导致整个程序退出,但在退出前,有 defer
延迟函数,还是得执行完 defer
。然后程序还会继续执行下去:
1 | runtime error: index out of range [20] with length 5 |
这里要注意一点,只有在相同的协程中调用 recover
才管用, recover
不能恢复一个不同协程的 panic
。
如何学习Go语言微服务,快速步入架构师


添加微信 | 公众号更多内容 |
---|---|
![]() |
![]() |