原文地址

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. 89C51单片机的学习

    好久都没来写一些东西了 最近老是忙着玩了,都忘记认真学习了. 大概从明天开始就要开始忙了. 1,英语四级 2,单片机课程 3,安卓课程 4,PS 感觉事情好多. 但是我还是心不在焉.好奇怪. 反正就是 ...

  2. ELK系统分析Nginx日志并对数据进行可视化展示

    结合之前写的一篇文章:ELK日志分析平台搭建全过程,上篇文章主要讲了部署方法.而这篇文章介绍的是单独监控nginx 日志分析再进行可视化图形展示. 本文环境与上一篇环境一样,前提 elasticsea ...

  3. ONOS的安装

    ONOS的简介 ONOS(Open Network Operating System)开放网络操作系统,由 ON.Lab 使用 Java 及 Apache 实现发布的首款开源的SDN网络操作系统. O ...

  4. Spring配置文件中的parent与abstract

    在看项目的Spring配置文件时,发现消息队列的配置采用了继承方式配置Bean,在这梳理总结一下. 其实在基于spring框架开发的项目中,如果有多个bean都是一个类的实例,如配置多个数据源时,大部 ...

  5. Redux 源码解读 —— 从源码开始学 Redux

    已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下:捡起旧知识的同时又有了一些新的收获,在这里作文以记之. 在阅读文章之前,最好已经知道如何使用 ...

  6. webview综述

    nWebView 是webkit最核心的一个view,WebView管理WebFrameView和WebFrame之间的交互,一个WebView对象绑定一个window,并且要求MainFrame加载 ...

  7. TensorFlow学习('utf-8' codec can't decode byte 0xff in position 0: invalid start byte)

    使用语句: image_raw_data = tf.gfile.GFile("./picture.jpg", "r").read() 读取图像时报错如下: Un ...

  8. [19/04/15-星期一] 基于Socket(套接字)的TCP和UDP通讯的实现

    一.TCP 在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序, 简称服务器.一旦通讯建立,则客户端和 ...

  9. notepad++怎样添加文件目录

    需要安装一个Explorer.dll文件 方法一:这个方法我个人试了不成功,可能因为版本问题,进入后选择Explorer进行安装 方法二:网上下载      Explorer.dll文件,放到\\No ...

  10. PHP+JQUERY+AJAX上传、裁剪图片(2)

    <script type="text/javascript"> var imgCut = { imgOpt : { imgPrototypeId : 'imgProto ...