go处理错误的另一种方式

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 错误处理 -- 使用异常的思路进行处理的更多相关文章

  1. golang 错误处理与异常

    原文地址 golang 中的错误处理的哲学和 C 语言一样,函数通过返回错误类型(error)或者 bool 类型(不需要区分多种错误状态时)表明函数的执行结果,调用检查返回的错误类型值是否是 nil ...

  2. ASP.NET全局错误处理和异常日志记录以及IIS配置自定义错误页面

    应用场景和使用目的 很多时候,我们在访问页面的时候,由于程序异常.系统崩溃会导致出现黄页.在通常的情况下,黄页对于我们来说,帮助是极大的,因为它可以帮助我们知道问题根源,甚至是哪一行代码出现了错误.但 ...

  3. spring boot: GlobalDefaultExceptionHandler方法内的友好错误提示,全局异常捕获

    spring boot: GlobalDefaultExceptionHandler方法内的友好错误提示,全局异常捕获 当你的某个控制器内的某个方法报错,基本上回显示出java错误代码,非常不友好,这 ...

  4. OpenJDK源码研究笔记(一)-参数检查&抛出带关键错误提示信息的异常

    OpenJDK源码研究笔记系列文章,是我在阅读OpenJDK7源码的过程中的一些体会.收获.看法. 把研究过程中的成长和收获一点点地整理出来,是对自己研究学习的一个小结,也有可能给学习Java的一些同 ...

  5. [golang]golang如何覆盖输出console,实现进度条;golang一个骚气的进度提示库

    [golang]golang如何覆盖输出console,实现进度条 package main import( "fmt" "os" "time&quo ...

  6. Go_19: Golang 中错误与异常需要重新认识

    如何进行错误处理,这是一个Go程序员之间,特别是一些新的Go程序员,会经常讨论的问题.讨论到最后往往由于以下代码的多次出现而变成了抱怨. if err != nil { return err } 我们 ...

  7. Golang 中错误与异常需要重新认识

    如何进行错误处理,这是一个Go程序员之间,特别是一些新的Go程序员,会经常讨论的问题.讨论到最后往往由于以下代码的多次出现而变成了抱怨. if err != nil { return err } 我们 ...

  8. [Golang]-4 错误处理、Panic、Defer

    目录 错误和异常 案例 Panic Defer 使用 defer+recover 来处理错误 参考文章: Go 语言使用一个独立的·明确的返回值来传递错误信息的.这与使用异常的 Java 和 Ruby ...

  9. 个人犯的一个golang routine错误

    这个其实不是错误,2个写法没有区别.-2015.11.22 认识golang也不少时间了,也做过几个项目.最近发现之前用golang写的一个服务,内存涨得比较快,一直没找出来原因来.今天把疑惑发到群里 ...

随机推荐

  1. getAttribute和getParameter的区别

    2016年1月19日JSP中getParameter与getAttribute有何区别? ——getParameter得到的都是String类型的.或者是http://a.jsp?id=123中的12 ...

  2. wampserver 绑定域名 外部可以正常访问

    wampserver使用的apache  绑定都一样 绑定的域名xuechao2010.gicp.net  端口是8989 系统是win7 64位 1.绑定域名 端口  在apache上 D:\wam ...

  3. FPM打包工具

    支持的源类型包: dir: 将目录打包成所需要的类型,可以用于源码编译安装的软件包 rpm: 对rpm进行转换 gem: 对rubygem包进行转换 python: 将Python模块打包成相应的类型 ...

  4. Nginx负载均衡配置实例详解(转)

    负载均衡是我们大流量网站要做的一个东西,下面我来给大家介绍在Nginx服务器上进行负载均衡配置方法,希望对有需要的同学有所帮助哦. 负载均衡 先来简单了解一下什么是负载均衡,单从字面上的意思来理解就可 ...

  5. gradle providedCompile 与compile区别

    Gradle compile: 如果你的jar包/依赖代码 在编译的时候需要依赖,在运行的时候也需要,那么就用compile例如 : compile 'org.springframework.boot ...

  6. 我再也不-或许永远不-用zend studio-受够了!

    zend studio背负的东西太多, 想要整合php, js, aptana, emmet, 还要git, 所以显得很累. 不过把这些它整合的东西 都用上后, 用好后, 倒确实是php的开发利器. ...

  7. 了不起的Node.js读书笔记

    原文摘自我的前端博客,欢迎大家来访问 http://www.hacke2.cn 第二章 Js概览 基于GoogleV8引擎 Object.keys(o) 数组方法:遍历forEach.过滤filter ...

  8. Linux运维初级教程(四)shell简介

    查看系统可用的shell命令 cat /etc/shells shell是用于与内核进行交流的工具 管道和重定向(< < > > |) |为管道 标准输入的文件描述符为0,标准 ...

  9. 不挣扎了,开始学习LINQ TO XML,进而来解析网页。

    找到了别人遇到和我一样的问题:http://ylad.codeplex.com/discussions/430095(英文) 一位叫做Mister Goodcat的提供了信息: Short answe ...

  10. 下位机多个".c, .h"文件的相互包含及排版

    一.背景: 自从接触单片机编程以来,由于工作上的需要,不可避免的时常会接手别人的代码,但常常由于上一位同事的编码随意性有点大,导致可读性非常的差,有时候不得不完全舍弃原有代码,推倒重来,无形中增加了工 ...