【GoLang】GoLang 错误处理 -- 使用异常的思路进行处理
2016-11-21 10:48
831 查看
go处理错误的另一种方式
go处理错误常见的方式是
然而因为过于繁琐而饱受诟病。下文简述另一种处理错误的写法。
这种写法最初我是从标准库里看到的,代码在 https://github.com/golang/go/blob/master/src/encoding/gob/error.go 。 简言之,就是将错误用panic抛出,然后在某个边界用defer将其转为error值。这和其他用抛异常来处理错误的语言类似。 不过上述代码并不十分通用,也没有解决最开始提出的写法繁琐的问题。 受其启发,我现在用得最多的错误处理方式是这样的 https://github.com/reusee/codes/blob/master/err/err.go 。
首先是Err结构体,定义如下
Pkg用于标识抛出错误的包,Info是对错误的描述。Err用于包装另一个错误,一般是当前函数所调用的函数返回的,可以实现类似java的chained exception的机制,后面再细说。
另外有一个me函数(make error),用于包装error,实现很简单不提。
然后是ce函数(check error)
这个函数检查err参数是否为nil,如果不是,则包装出一个Err结构,然后用panic抛出。 这个就是用于替代if err != nil { … }的了。
错误用panic抛出后,必须在某个边界recover,API不应该对外暴露panic,否则会和go社区整体的理念不合,自找烦恼。 负责这个的是ct函数(catch error)
因为用到了recover,所以ct只能在defer函数里调用。它首先recover(),然后看是否是error,是则将其赋值到传入的*error处,否则重新panic抛出
来看看它是如何减少代码的,以 https://blog.golang.org/errors-are-values 的一段代码为例
用上述机制,可以写成
代码没有那样繁琐了。
另外还有一个好处是,因为Err包装了上一个错误,所以定位错误比较容易。例如下面程序
paniclog是这样的
可以看出最外层的error包含了直到最内层的信息,包括包名foobar(这里只用到一个包所以体现不出),比起直接将最内层的error往上抛,要直观得多。
最后说说这种写法的缺点。首先是不论有无错误都调用recover,调用recover又要使用defer函数,所以性能会受到影响。 另外因为没有 if 语句了,做覆盖测试的话,区分不出两种case了。 所以这种写法并不适合所有场景。需要压榨性能时不用,需要做覆盖测试时不用。 适合的场景是对性能要求不高的,对正确性要求也不高的。 我会用在经常变的应用代码,或者百几十行的小程序,或者测试代码里。 基础的包,还是好好写 if err != nil { … } 吧。
以上ct、me、ce等函数都不是public的,因为我使用时,是用代码生成工具复制出来用的,不需要public。 用的代码生成工具是 https://github.com/reusee/ccg ,可能会有另外一篇博文说说这个。
参考资料:
http://reusee.github.io/post/error-handling/
go处理错误常见的方式是
err := funcReturningError() if err != nil { // 处理错误 }
然而因为过于繁琐而饱受诟病。下文简述另一种处理错误的写法。
这种写法最初我是从标准库里看到的,代码在 https://github.com/golang/go/blob/master/src/encoding/gob/error.go 。 简言之,就是将错误用panic抛出,然后在某个边界用defer将其转为error值。这和其他用抛异常来处理错误的语言类似。 不过上述代码并不十分通用,也没有解决最开始提出的写法繁琐的问题。 受其启发,我现在用得最多的错误处理方式是这样的 https://github.com/reusee/codes/blob/master/err/err.go 。
首先是Err结构体,定义如下
type Err struct { Pkg, Info string Err error }
Pkg用于标识抛出错误的包,Info是对错误的描述。Err用于包装另一个错误,一般是当前函数所调用的函数返回的,可以实现类似java的chained exception的机制,后面再细说。
另外有一个me函数(make error),用于包装error,实现很简单不提。
然后是ce函数(check error)
func ce(err error, info string) { if err != nil { panic(me(err, info)) } }
这个函数检查err参数是否为nil,如果不是,则包装出一个Err结构,然后用panic抛出。 这个就是用于替代if err != nil { … }的了。
错误用panic抛出后,必须在某个边界recover,API不应该对外暴露panic,否则会和go社区整体的理念不合,自找烦恼。 负责这个的是ct函数(catch error)
func ct(err *error) { if p := recover(); p != nil { if e, ok := p.(error); ok { *err = e } else { panic(p) } } }
因为用到了recover,所以ct只能在defer函数里调用。它首先recover(),然后看是否是error,是则将其赋值到传入的*error处,否则重新panic抛出
来看看它是如何减少代码的,以 https://blog.golang.org/errors-are-values 的一段代码为例
_, err = fd.Write(p0[a:b]) if err != nil { return err } _, err = fd.Write(p1[c:d]) if err != nil { return err } _, err = fd.Write(p2[e:f]) if err != nil { return err }
用上述机制,可以写成
defer ct(&err) _, err = fd.Write(p0[a:b]) ce(err, "write p0") _, err = fd.Write(p1[c:d]) ce(err, "write p1") _, err = fd.Write(p2[e:f]) ce(err, "write p2")
代码没有那样繁琐了。
另外还有一个好处是,因为Err包装了上一个错误,所以定位错误比较容易。例如下面程序
package main func foo() (err error) { defer ct(&err) ce(bar(), "call bar") return } func bar() (err error) { defer ct(&err) ce(baz(), "call baz") return } func baz() (err error) { return me(nil, "baz") } func main() { ce(foo(), "call foo") }
paniclog是这样的
panic: foobar: call bar foobar: call baz foobar: baz ...
可以看出最外层的error包含了直到最内层的信息,包括包名foobar(这里只用到一个包所以体现不出),比起直接将最内层的error往上抛,要直观得多。
最后说说这种写法的缺点。首先是不论有无错误都调用recover,调用recover又要使用defer函数,所以性能会受到影响。 另外因为没有 if 语句了,做覆盖测试的话,区分不出两种case了。 所以这种写法并不适合所有场景。需要压榨性能时不用,需要做覆盖测试时不用。 适合的场景是对性能要求不高的,对正确性要求也不高的。 我会用在经常变的应用代码,或者百几十行的小程序,或者测试代码里。 基础的包,还是好好写 if err != nil { … } 吧。
以上ct、me、ce等函数都不是public的,因为我使用时,是用代码生成工具复制出来用的,不需要public。 用的代码生成工具是 https://github.com/reusee/ccg ,可能会有另外一篇博文说说这个。
参考资料:
http://reusee.github.io/post/error-handling/
相关文章推荐
- 使用两个不同类型的数据进行加法计算时,使用异常处理语句捕获由于数据类型错误而出现的异常,发生生成错误。是否继续并运行上次的成功生成?
- 【GoLang】GoLang 错误处理 -- 使用 error is value 的思路处理,检查并处理error
- 【GoLang】GoLang 错误处理 -- 异常处理思路示例
- 使用企业类库进行异常处理
- 使用IErrorHandle对WCF服务器进行异常处理
- 使用 Flex 和 Bison 更好地进行错误处理
- sql server try...catch使用 异常处理 不错的SQL错误处理
- 使用 Flex 和 Bison 更好地进行错误处理
- 在使用Microsoft Visual Studio Test Controller 2010进行测试时,报如下错误: System.DllNotFoundException: 无法加载 DLL“Microsoft.VisualStudio.QualityTools.RecorderBarBHO100.dll”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)。
- 使用Try…Catch块进行T-SQL错误处理
- 使用springMVC进行统一的异常处理
- GoldenGate 使用reperror进行错误处理
- asp.net访问access 发生了未处理的异常 "操作必须使用一个可更新的查询"错误
- 在SQL Server 2005中使用Try…Catch块进行T-SQL错误处理
- 使用struts2 做客户端校验时抛出freemarker模版错误异常的处理
- PHP5的异常处理机制[5]--PHP5之前的错误处理--使用PEAR处理错误
- Linux系统平台下关于GCC编译及使用的方法(-Werror,它要求GCC将所有的警告当成错误进行处理 )
- Linux系统平台下关于GCC编译及使用的方法(-Werror,它要求GCC将所有的警告当成错误进行处理 )
- 使用HttpWebRequest进行请求时发生错误:基础连接已关闭,发送时发生错误处理
- Linux系统平台下关于GCC编译及使用的方法 (-Werror,它要求GCC将所有的警告当成错误进行处理