GoLang 中的异常与处理 -- 错误和宕机

2019-11-08 13:57:26   最后更新: 2019-11-14 17:01:10   访问数量:44




GoLang 语言和其他很多语言不通,他并没有异常机制

那么,在 GoLang 语言中,如何处理随时可能发生的错误呢?

本文我们详细来介绍一下

 

 

正如上面所说,GoLang 中没有类似 java 那样的异常处理机制

熟悉 java 的程序员可能对此很不习惯,但事实上,异常处理机制也始终处在争议之中

java 中的异常分为 Error 和 Exception,Error 指的是程序无法处理的严重错误,通常与代码操作无关,JVM 会中止线程执行,因此并不是我们这里讨论的程序中的异常处理

java 中的 Exception 则分为两种:

  1. IOException -- 函数必须显式声明,一旦函数有可能抛出 IOException,外层必须编写处理逻辑 -- 捕获或继续向外抛出
  2. RuntimeException -- 无需显式声明,外层也可以不必显式处理

 

在实际使用中,通常大量使用的是 IOException,因为显式抛出让代码显得更具安全性与可控性,但为此必须付出的代价则是大量异常处理的代码逻辑

而 RuntimeException 则更为接近 Python、Ruby 等语言的处理方式,在函数声明中并不显式声明将要抛出的异常,外层可以对相应的异常做出处理,也可以不关心这些异常,任由其继续向外抛出

这两种处理方式其实是两种程序设计模式的区别:

  1. 可靠性驱动 -- reliability-driven
  2. 可维护性驱动 -- maintenance-driven

 

GoLang 也是典型的可维护性驱动的,他不建议使用其他语言抛出异常外层捕获处理的方式来进行异常情况的恢复和处理,而是通过返回参数判断

因为异常堆栈中的大量信息通常并不是使用者所关心的,而对于错误的恢复也并不会起到什么作用

 

golang 标准库中预先定义了一个接口 -- error:

type error interface { Error() string }

 

 

这个接口中只有一个方法,Error() 用来返回错误信息

 

在 GoLang 中,通常通过返回 error 实例的方式来实现函数中错误信息的返回:

func div(num1 int, num2 int) (result, remainder int, err error) { if num2 == 0 { return 0, 0, fmt.Errorf("divisor cannot be 0") } isneg, quotneg := num1 > 0 && num2 < 0 || num1 < 0 && num2 > 0, false if num1 < 0 { num1 = neg(num1) } if num2 < 0 { quotneg = true num2 = neg(num2) } unum1, unum2 := uint(num1), uint(num2) for i:=31; i>=0; i = sub(i, 1) { j := unum1 >> uint(i) if j >= unum2 { result += 1 << uint(i) unum1 = uint(sub(int(unum1), int(unum2<<uint(i)))) if unum1 < unum2 { break } } } num1 = int(unum1) if isneg { result = neg(result) } if quotneg { num1 = neg(num1) } return result, num1, nil }

 

 

上面的例子展示了通过二进制的方式实现两数除法的代码,返回值列表中最后一个是 error 类型的 err 字段,在传入的除数为 0 时,该字段会返回错误信息:

divisor cannot be 0

 

标准库 fmt 包中有一个方法 Errorf 方便的实现了 error 类型对象的构建

 

错误处理通常有以下几种方式

 

继续传递

这是很常见的一种情况,例如你的函数中依赖某个必须的数据源,当获取相应数据失败时,将失败信息直接返回是一种最简单的处理方式

datas, err := http.Get(url) if err != nil { return nil, err }

 

 

追加信息后返回

针对上面的情况,我们可能不知道一场发生的具体时机,那么外层增加一些信息后返回看上去就是一个很好的选择

datas, err := http.Get(url) if err != nil { return nil, fmt.Errorf("parsing %s error: %v", url, err) }

 

 

这在 GoLang 中是非常常见的,因此,规范建议不要将错误信息的首字母大写,因为他们可能被嵌套在外层信息之中,同时,为了在 log 中更好地定位报错信息,建议不要在错误信息中换行

 

直接中止程序执行

通常这样的处理方式发生在主程序部分

if err != nil { log.Fatalf("Site is down: %v\n", err) os.Exit(1) }

 

 

此时日志中会打印出:

2019/11/17 15:04:05 Site is down: parsing techhlog.cn error: socket timeout

 

继续执行

对于非关键异常,仅记录异常发生,不做任何处理有时是正确的处理方式

if err := Ping(); err != nil { fmt.Fprintf(os.Stderr, "ping failed: %v", err) }

 

 

一个典型的例子是文件读取,我们必须检测 error 参数,判断是否读取到了文件的末尾,从而判断是否需要中止继续进行读写

in := bufio.NewReader(os.Stdin) for { r, _, err := in.ReadRune() if err == io.EOF { break } if err != nil { return fmt.Errorf("read file failed: %v", err) } }

 

 

完全忽略异常的发生

通常不建议忽略函数显式返回的 error 参数

可以通过占位符 "_" 来实现相应参数的忽略

resposne, _ := http.Get(url)

 

 

宕机 -- panic

通常,在程序执行中发生错误时就会触发宕机,包括数组越界或解引用空指针等

当一个宕机发生时,正常的程序执行会中止,goroutine 中所有的延迟函数会执行,然后程序返回一条日志信息并退出

通过 GoLang 内置的 panic 函数,我们可以手动触发一次宕机事件的发生:

func isWorkingDay(dateindex int) bool { if dateindex < 0 || dateindex > 6 { panic("dateindex invalid %d", dateindex) } return dateindex > 0 && dateindex <=5 }

 

 

GoLang 内置的 panic 函数让我们实现了 C 语言 assert 函数的功能,对于程序中绝不会进入的逻辑,设置断言是一个好的习惯,但这同时会增加一定的维护成本,且在发生时可能造成程序的异常退出,在使用时,还是应该去谨慎考虑

 

宕机的恢复

通常,直接提出程序执行是发生宕机的正确处理方式,但如果程序是服务端常驻进程,退出执行的后果可能是无法接受的

内置函数 recover 会终止当前的宕机状态并返回宕机的值,将 recover 函数的调用放到 goroutine 的延迟函数中,就可以实现宕机的恢复机制

func Parse(input string) (s *Syntax, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v", p) } } }

 

 

panic 与 recover 两个内置函数搭配,看上去就可以实现其他语言中的异常处理机制了,但宕机机制在 GoLang 中并非用于异常情况的报告与恢复,其设计理念在于不可恢复问题或不可能发生情况的报告与程序的中止运行,并不是常见问题的发现与处理,通常情况下,仍然建议通过返回 error 实例来进行执行中问题的返回与处理

 

函数的延迟调用 -- defer

上面的代码中使用到了 defer,这个关键字用来声明该函数延迟到以下时机再进行调用:

  1. 包裹 defer 的函数返回时
  2. 包裹 defer 的函数执行到末尾时
  3. 所在的 goroutine 发生 panic 时

 

但调用 os.Exit() 方法将不会触发 defer 函数的执行

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 

https://www.sciencedirect.com/science/article/pii/S0164121215000862#!

https://www.researchgate.net/publication/282329720_How_Does_Exception_Handling_Behavior_Evolve_An_Exploratory_Study_in_Java_and_C_Applications

 






读书笔记      exception      技术贴      异常      golang      宕机     


京ICP备15018585号