原文地址

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

error

golang 中内置的错误类型 error 是一个接口类型,自定义的错误类型也必须实现为 error 接口,这样调用总是可以通过 Error() 获取到具体的错误信息而不用关心错误的具体类型。标准库的 fmt.Errorferrors.New 可以方便的创建 error 类型的变量。

type error interface {
Error() string
}

C 语言的语法限制函数只能有一个返回值,函数的执行结果和执行成功时需要返回的信息都放到这个返回值里,具体的错误信息需要调用额外的接口获取。比如 C 标准库函数读取文件的函数 read 返回 -1 时表示读取错误,具体错误信息需要通过 errno 获取,返回 0 时,表示 EOF ,返回正整数时,表示成功读取到的字节数。golang 的多返回值语法糖避免了这种方式带来的不便,错误值一般作为返回值列表的最后一个,其他返回值是成功执行时需要返回的信息。为了避免错误处理时过深的代码缩进:

if err != nil {
// error handling
} else {
// normal code
}

推荐在发生错误时立即返回:

if err != nil {
// error handling
return // or continue, etc.
}
// normal code

虽然这种错误处理方式代码写起来会有一些冗长,但 golang 风格的代码应该尽量使用这种方式。

预定义错误值

下面的示例代码中,当需要返回错误时,每次都调用 errors.New() 返回一个 error 对象:

func doStuff() error {
if someCondition {
return errors.New("no space left on the device")
} else {
return errors.New("permission denied")
}
}

这种做法的问题是当需要对特定错误进行处理时,不方便对错误值进行等值判断。只能用 Error() 取出字符串比较,这样即不优雅也容易出现拼写错误。最佳的做法是预定义全局的错误值:

var ErrNoSpaceLeft = errors.New("no space left on the device")
var ErrPermissionDenied = errors.New("permission denied") func doStuff() error {
if someCondition {
return ErrNoSpaceLeft
} else {
return ErrPermissionDenied
}
}

这样错误值判断就方便多了:

if err == ErrNoSpaceLeft {
// handle this particular error
}

标准库中也预定义了一些错误值 ,最常用的就是 io.EOF

自定义错误类型

HTTP 表示客户端的错误状态码有几十个。如果为每种状态码都预定义相应的错误值,代码会变得很繁琐:

var ErrBadRequest = errors.New("status code 400: bad request")
var ErrUnauthorized = errors.New("status code 401: unauthorized")
// ...

这种场景下最佳的最法是自定义一种错误类型,并且至少实现 Error() 方法(满足 error 定义):

type HTTPError struct {
Code int
Description string
} func (h *HTTPError) Error() string {
return fmt.Sprintf("status code %d: %s", h.Code, h.Description)
}

这种方式下进行等值判断时需要转成具体的自定义类型然后取出 Code 字段判断:

func request() error {
return &HTTPError{404, "not found"}
} func main() {
err := request() if err != nil {
// an error occured
if err.(*HTTPError).Code == 404 {
// handle a "not found" error
} else {
// handle a different error
}
} }

自定义错误类型可以提供更多特定的错误信息,标准库中也有一个这样的例子 os.PathError

panic

golang 新手很容易把 panic 当成其他语言的异常(exception)导致 panic 的滥用。这里解释了为什么 golang 没有提供类似其他语言的异常机制的原因:主要是 try / catch 的方式处理错误过于复杂。使用 panic / recover 的机制也可以达到类似 try / catch 的效果,但是要特别谨慎的使用。

panic / recover 和 try / catch 机制最大的不同在于控制流程上的区别。try / catch 机制控制流作用在 try 代码块内,代码块执行到异常抛出点(throw)时,控制流跳出 try 代码块,转到对应的 catch 代码块,然后继续往下执行。panic / recover 机制控制流则作用在整个 goroutine 的调用栈。当 goroutine 执行到 panic 时,控制流开始在当前 goroutine 的调用栈内向上回溯(unwind)并执行每个函数的 defer 。如果 defer 中遇到 recover 则回溯停止,如果执行到 goroutine 最顶层的 defer 还没有 recover ,运行时就输出调用栈信息然后退出。所以如果要使用 recover 避免 panic 导致进程挂掉,recover 必须要放到 defer 里。

recover 调用的位置如果不对是无法将 panic 恢复的,这里有个比较复杂的规则,具体可以参考这里。为了避免过于复杂的代码,最好不要使用嵌套的 defer ,并且 recover 应该直接放到 defer 函数里直接调用。

panic 主要用于以下场景:

  1. 发生严重错误(critical error)必须让进程退出。这里“严重”判断的标准是错误无法恢复导致程序无法执行或者继续执行会发生不可预期的行为。有点类似如 C 语言的 assert 机制。标准库中有些函数也是使用这种机制,比如 regexp.MustCompile 。当传入的参数不是一个合法的正则表达式时,继续执行已经没有任何意义。另外一些场景,比如程序启动时依赖的数据库不存在或者依赖的配置不可读取,这个时候如果继续执行可能会导致发生不可预期的行为,这个时候使用 panic 让进程直接退出将问题暴露反而是更可取的做法。非“严重”的错误比如客户端不合法的请求参数应该返回 error 然后让调用者去处理而不是使用 panic 让进程退出。
  2. 快速退出错误处理。也就是上文中提到的模拟 try / catch 的行为。大多数情况下错误处理应该使用 error 机制,但有时候函数调用栈很深,逐层返回错误可能需要写很多冗余代码,这个时候可以使用 panic 让程序的控制流直接跳到顶层的 recover 处来处理错误。这种场景要特别注意在包内的 panic 必须在包内就要 recover 。让 panic 跨包传递可能会导致更复杂的问题,所以包的导出函数不应该产生 panic (上述 1 中的场景除外)。

总结

本文简要介绍了 golang 的错误处理与异常机制。error 是 golang 中错误处理的主要方式,golang 程序应该尽量使用 error 来进行错误处理。当需要对 error 进行等值判断以针对特定的错误类型进行处理处理时,预定义全局的错误值是比较优雅的方式。当需要提供更多额外的错误信息时,可以自定义错误类型,但至少要实现 Error() 方法以满足 error 的定义。golang 虽然也有类似其他语言的异常的 panic ,但是使用场景很有限,如非必要,不要使用。

参考资料

golang 错误处理与异常的更多相关文章

  1. 【GoLang】GoLang 错误处理 -- 使用异常的思路进行处理

    go处理错误的另一种方式 go处理错误常见的方式是 err := funcReturningError() if err != nil { // 处理错误 } 然而因为过于繁琐而饱受诟病.下文简述另一 ...

  2. Golang错误和异常处理的正确姿势

    Golang错误和异常处理的正确姿势 错误和异常是两个不同的概念,非常容易混淆.很多程序员习惯将一切非正常情况都看做错误,而不区分错误和异常,即使程序中可能有异常抛出,也将异常及时捕获并转换成错误.从 ...

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

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

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

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

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

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

  6. 窥探Swift编程之错误处理与异常抛出

    在Swift 2.0版本中,Swift语言对其错误处理进行了新的设计,当然了,重新设计后的结果使得该错误处理系统用起来更爽.今天博客的主题就是系统的搞一下Swift中的错误处理,以及看一下Swift中 ...

  7. FreeRTOS 中断优先级嵌套错误引发HardFault异常解决(转)

      最近在使用FreeRTOS的时候,突然发现程序在运行了几分钟之后所有的任务都不再调用了,只有几个中断能正常使用,看来是系统挂掉了,连续测试了几次想找出问题,可是这个真的有点不知所措.      我 ...

  8. 【GoLang】GoLang 错误处理 -- 官方推荐方式 示例

    最严谨的方式,Always检查error,并做相应的处理 项目结构: 代码: common.go: package common import ( "github.com/pkg/error ...

  9. FreeRTOS 中断优先级嵌套错误引发HardFault异常解决

          最近在使用FreeRTOS的时候,突然发现程序在运行了几分钟之后所有的任务都不再调用了,只有几个中断能正常使用,看来是系统挂掉了,连续测试了几次想找出问题,可是这个真的有点不知所措.   ...

随机推荐

  1. 乘风破浪:LeetCode真题_001_TwoSum

    乘风破浪:LeetCode真题_001_TwoSum 一.前言 沉寂了很长时间,也悟出了很多的道理,写作是一种业余的爱好,是一种自己以后学习的工具,是对自己过往的经验积累的佐证,是检验自己理解深入度的 ...

  2. 在python中逐行读取大文件

    在我们日常工作中,难免会有处理日志文件的时候,当文件小的时候,基本不用当心什么,直接用file.read()或readlines()就可以了,但是如果是将一个10G大小的日志文件读取,即文件大于内存的 ...

  3. HTTP协议图--HTTP 工作过程

                  HTTP请求响应模型 HTTP通信机制是在一次完整的 HTTP 通信过程中,客户端与服务器之间将完成下列7个步骤: 建立 TCP 连接 在HTTP工作开始之前,客户端首先要 ...

  4. 用ABAP代码读取S/4HANA生产订单工序明细

    在S/4HANA事务码CO03显示的Production Order里,我希望用ABAP代码显示出该订单的operation(工序)ID,描述和状态Status,如下图所示: 很简单的几行ABAP代码 ...

  5. 如何把GitHub中的开源项目导入到Eclipse

    准备: 1.需要注册GitHub的账号,并找到自己想导入的项目 2.在Eclipse的help-->Marketplace中搜索egit插件,然后安装 操作步骤: 1.有三种导入方式HTTP.S ...

  6. linux服务器nginx的卸载和安装

    刚接触的linux服务器上,nginx配置乱的有点令人发指,就把老的卸载了重新装一下. 卸载 linux有一系列的软件管理器,比如常见的linux下的yum.Ubuntu下的apt-get等等.通过这 ...

  7. oracle exp 无法导出空表

    oracle exp 无法导出空表   select 'alter table '|| a.table_name ||' allocate extent;' from user_tables a wh ...

  8. [emerg]: getpwnam(“nginx”) failed

    [root@localhost nginx-1.11.2]# /usr/local/nginx/sbin/nginx nginx: [emerg] getpwnam("nginx" ...

  9. BZOJ1089:[SCOI2003]严格n元树(DP,高精度)

    Description 如果一棵树的所有非叶节点都恰好有n个儿子,那么我们称它为严格n元树.如果该树中最底层的节点深度为d (根的深度为0),那么我们称它为一棵深度为d的严格n元树.例如,深度为2的严 ...

  10. bzoj 4712: 洪水

    [权限题][https://www.lydsy.com/JudgeOnline/status.php?problem_id=4712&jresult=4] 这道动态\(dp\)终于不是独立集/ ...