Go 语言的错误处理
Go 语言的函数支持多返回值,所以,可以在返回接口把业务语义(业务返回值)和控制语义(出错返回值)区分开。Go 语言的很多函数都会返回 result、err 两个值,于是就有这样几点:
-
参数上基本上就是入参,而返回接口把结果和错误分离,这样使得函数的接口语义清晰;
-
而且,Go 语言中的错误参数如果要忽略,需要显式地忽略,用 _ 这样的变量来忽略;
-
另外,因为返回的 error 是个接口(其中只有一个方法 Error(),返回一个 string ),所以你可以扩展自定义的错误处理。
另外,如果一个函数返回了多个不同类型的 error,你也可以使用下面这样的方式:
if err != nil {
switch err.(type) {
case *json.SyntaxError:
...
case *ZeroDivisionError:
...
case *NullPointerError:
...
default:
...
}
}
Go 语言的错误处理的方式,本质上是返回值检查,但是它也兼顾了异常的一些好处——对错误的扩展。
资源清理
出错后是需要做资源清理的,不同的编程语言有不同的资源清理的编程模式。
C 语言:使用的是 goto fail; 的方式到一个集中的地方进行清理.
C++ 语言:一般来说使用 RAII 模式,通过面向对象的代理模式,把需要清理的资源交给一个代理类,然后再析构函数来解决。
Java 语言:可以在 finally 语句块里进行清理。
Go 语言:使用 defer 关键词进行清理。
下面是一个 Go 语言的资源清理的示例:
func Close(c io.Closer) {
err := c.Close()
if err != nil {
log.Fatal(err)
}
}
func main() {
r, err := Open("a")
if err != nil {
log.Fatalf("error opening 'a'\n")
}
defer Close(r) // 使用defer关键字在函数退出时关闭文件。
r, err = Open("b")
if err != nil {
log.Fatalf("error opening 'b'\n")
}
defer Close(r) // 使用defer关键字在函数退出时关闭文件。
}
Error Check Hell
说到 Go 语言的 if err !=nil 的代码了,这样的代码的确是能让人写到吐。那么有没有什么好的方式呢?有的。我们先看一个令人崩溃的代码。
func parse(r io.Reader) (*Point, error) {
var p Point
if err := binary.Read(r, binary.BigEndian, &p.Longitude); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.Latitude); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.Distance); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.ElevationGain); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.ElevationLoss); err != nil {
return nil, err
}
}
要解决这个事,我们可以用函数式编程的方式,如下代码示例:
func parse(r io.Reader) (*Point, error) {
var p Point
var err error
read := func(data interface{}) {
if err != nil {
return
}
err = binary.Read(r, binary.BigEndian, data)
}
read(&p.Longitude)
read(&p.Latitude)
read(&p.Distance)
read(&p.ElevationGain)
read(&p.ElevationLoss)
if err != nil {
return &p, err
}
return &p, nil
}
从这段代码中,我们可以看到,我们通过使用 Closure 的方式把相同的代码给抽出来重新定义一个函数,这样大量的 if err!=nil 处理得很干净了。