mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 20:52:22 +00:00
commit
be10ffdd88
@ -27,7 +27,7 @@ if err != nil {
|
|||||||
|
|
||||||
在Go语言中,错误被认为是一种可以预期的结果;而异常则是一种非预期的结果,发生异常可能表示程序中存在BUG或发生了其它不可控的问题。Go语言推荐使用`recover`函数将内部异常转为错误处理,这使得用户可以真正的关心业务相关的错误处理。
|
在Go语言中,错误被认为是一种可以预期的结果;而异常则是一种非预期的结果,发生异常可能表示程序中存在BUG或发生了其它不可控的问题。Go语言推荐使用`recover`函数将内部异常转为错误处理,这使得用户可以真正的关心业务相关的错误处理。
|
||||||
|
|
||||||
如果某个接口简单地将所有普通的错误当做异常抛出,将会错误的信息杂乱没有价值。就像在`main`函数中直接捕获全部一样,是没有意义的:
|
如果某个接口简单地将所有普通的错误当做异常抛出,将会使错误信息杂乱且没有价值。就像在`main`函数中直接捕获全部一样,是没有意义的:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -86,9 +86,9 @@ func CopyFile(dstName, srcName string) (written int64, err error) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`defer`语言可以让我们在打开文件时马上思考如何关闭文件。不管函数如何返回,文件关闭语句始终会被执行。同时`defer`语句可以保证,即使`io.Copy`发生了异常,文件依然可以安全地关闭。
|
`defer`语句可以让我们在打开文件时马上思考如何关闭文件。不管函数如何返回,文件关闭语句始终会被执行。同时`defer`语句可以保证,即使`io.Copy`发生了异常,文件依然可以安全地关闭。
|
||||||
|
|
||||||
前文我们说到,Go语言中的导出函数一般不抛出异常,一个未受控的异常可以看作是程序的BUG。但是对于有一些提供类似Web服务的框架而言;它们经常需要接入第三方的中间件。因为第三方的中间件是否存在BUG是否会抛出异常,Web框架本身是不能确定的。为了提高系统的稳定性,Web框架一般会通过`recover`来防御性地捕获所有处理流程中可能产生的异常,然后将异常转为普通的错误返回。
|
前文我们说到,Go语言中的导出函数一般不抛出异常,一个未受控的异常可以看作是程序的BUG。但是对于那些提供类似Web服务的框架而言;它们经常需要接入第三方的中间件。因为第三方的中间件是否存在BUG是否会抛出异常,Web框架本身是不能确定的。为了提高系统的稳定性,Web框架一般会通过`recover`来防御性地捕获所有处理流程中可能产生的异常,然后将异常转为普通的错误返回。
|
||||||
|
|
||||||
让我们以JSON解析器为例,说明recover的使用场景。考虑到JSON解析器的复杂性,即使某个语言解析器目前工作正常,也无法肯定它没有漏洞。因此,当某个异常出现时,我们不会选择让解析器崩溃,而是会将panic异常当作普通的解析错误,并附加额外信息提醒用户报告此错误。
|
让我们以JSON解析器为例,说明recover的使用场景。考虑到JSON解析器的复杂性,即使某个语言解析器目前工作正常,也无法肯定它没有漏洞。因此,当某个异常出现时,我们不会选择让解析器崩溃,而是会将panic异常当作普通的解析错误,并附加额外信息提醒用户报告此错误。
|
||||||
|
|
||||||
@ -119,9 +119,9 @@ if _, err := html.Parse(resp.Body); err != nil {
|
|||||||
|
|
||||||
上层用户在遇到错误时,可以很容易从业务层面理解错误发生的原因。但是鱼和熊掌总是很难兼得,在上层用户获得新的错误的同时,我们也丢失了底层最原始的错误类型(只剩下错误描述信息了)。
|
上层用户在遇到错误时,可以很容易从业务层面理解错误发生的原因。但是鱼和熊掌总是很难兼得,在上层用户获得新的错误的同时,我们也丢失了底层最原始的错误类型(只剩下错误描述信息了)。
|
||||||
|
|
||||||
为了记录这种错误类型在包装的变迁过程中的信息,我们一般会定义一个辅助的`WrapError`函数,用于包装原始的错误,同时保留完整的原始错误类型。为了问题定位的方便,同时也为了能记录错误发生时的函数调用状态,我们很多时候希望在出现致命错误的时候保存完整的函数调用信息。同时,为了支持RPC等跨网络的传输,我们可能要需要将错误序列号为为类似JSON的数据,然后再从表示原始错误的JSON数据中解码恢复错误。
|
为了记录这种错误类型在包装的变迁过程中的信息,我们一般会定义一个辅助的`WrapError`函数,用于包装原始的错误,同时保留完整的原始错误类型。为了问题定位的方便,同时也为了能记录错误发生时的函数调用状态,我们很多时候希望在出现致命错误的时候保存完整的函数调用信息。同时,为了支持RPC等跨网络的传输,我们可能要需要将错误序列化为类似JSON格式的数据,然后再从这些数据中将错误解码恢复出来。
|
||||||
|
|
||||||
为此,我们可以定义自己的`github.com/chai2010/errors`包,里面以下的错误类型:
|
为此,我们可以定义自己的`github.com/chai2010/errors`包,里面是以下的错误类型:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
||||||
@ -287,7 +287,7 @@ func panic(interface{})
|
|||||||
func recover() interface{}
|
func recover() interface{}
|
||||||
```
|
```
|
||||||
|
|
||||||
Go语言函数调用的正常流程是函数执行返回语句返回结果,在这个流程中是没有异常的,因此在这个流程中执行`recover`异常捕获函数始终是返回`nil`。另一种是异常流程: 当函数调用`panic`抛出异常,函数将停止执行后续的普通语句,但是之前注册的`defere`函数调用仍然保证会被正常执行,然后再返回到的调用者。对于当前函数的调用者,因为处理异常状态还没有被捕获,和直接调用`panic`函数的行为类似。在异常发生时,如果在`defer`中执行`recover`调用,它可以捕获触发`panic`时的参数,并且恢复到正常的执行流程。
|
Go语言函数调用的正常流程是函数执行返回语句返回结果,在这个流程中是没有异常的,因此在这个流程中执行`recover`异常捕获函数始终是返回`nil`。另一种是异常流程: 当函数调用`panic`抛出异常,函数将停止执行后续的普通语句,但是之前注册的`defer`函数调用仍然保证会被正常执行,然后再返回到的调用者。对于当前函数的调用者,因为处理异常状态还没有被捕获,和直接调用`panic`函数的行为类似。在异常发生时,如果在`defer`中执行`recover`调用,它可以捕获触发`panic`时的参数,并且恢复到正常的执行流程。
|
||||||
|
|
||||||
在非`defer`语句中执行`recover`调用是初学者常犯的错误:
|
在非`defer`语句中执行`recover`调用是初学者常犯的错误:
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user