Go语言学习四——异常处理
Posted 自由水鸟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言学习四——异常处理相关的知识,希望对你有一定的参考价值。
程序就像汽车,一旦上路,难免会有各种大大小小的突发情况:路上突然闯出一个行人、没油了、零部件故障甚或剐蹭追尾、车毁人亡。现代的汽车已经针对各种情况作出了不同的预案:鸣笛警示行人、将近没油时进行提示、汽车启动时检查核心部件,在最糟糕的情况下,也会弹出安全气囊尽量减少损伤。更智能的无人驾驶技术甚至可以自动减速、主动避让行人。
好的程序语言就像好的汽车,为我们提供了多层次的安全预案与安全保障。早期的C语言并没有直接提供语言层面的异常处理,更多需要程序员自己判断异常情况并返回errorCode,或者使用goto语句跳转到错误处理逻辑进行错误处理;经典的面向对象语言如Java通常提供了更完备的错误处理机制:明确不同的错误类型,并提供语言级别的异常捕获(try-catch-finally)机制,在很大程度上减轻了程序员的负担,提供了程序的健壮性。
作为一门更年轻也非常有个性的编程语言,Go语言借助自己函数多返回值、函数式编程以及灵活的接口等特性,提供了非常优雅的异常处理方式。
一. 异常接口
Go语言内置了基础的error数据类型,定义如下:
type error interface {
Error() string
}
可以看出,error类型是一个接口,其中声明了一个Error
方法,用来获取错误信息。
基于这个接口,我们可以声明自己的error类型,如下:
type MyError struct {
modle string
err error
}
func (e *MyError) Error() string {
return fmt.Sprintf("[%s]%s", e.modle, e.err.Error())
}
基于error扩展的错误类型可以包含更多的错误信息,便于更好的分析、定位问题。
有了error类型之后,我们在遇到错误时,就可以返回error了,由于Go语言中的函数支持返回多个值,因此,通常函数设计时都会连同正常结果与错误信息一起返回,如下:
func readFile(file string) (string, error) {
byteArray, err := ioutil.ReadFile(file)
if err != nil { // 读取文件错误
return "", &MyError{"FILEREAD", err}
} else { // 读取文件正常
return string(byteArray), nil
}
}
二. defer
程序中一些特殊的操作需要在处理完成后执行清理,例如打开文件、网络连接后需要关闭,但是一旦程序在执行过程中出错,这些清理操作很有可能得不到执行,造成文件句柄得不到关闭,网络连接得不到释放的情况,对系统整体的性能及稳定性都有很大影响。为了解决这个问题,Go引入了defer关键字,在作用上有点类似于java中的finally
。我们直接看一下上面代码中用到的ioutil.ReadFile
方法代码:
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close() // 确保文件被正常关闭
var n int64 = bytes.MinRead
if fi, err := f.Stat(); err == nil {
if size := fi.Size() + bytes.MinRead; size > n {
n = size
}
}
return readAll(f, n)
}
函数中在打开文件后声明了defer语句用于关闭文件,则即使函数在后面执行过程中出错,该语句也会被执行。每当声明一个defer语句时,相当于向函数注册一个延迟执行命令,等函数执行完毕或者异常退出时将执行这些指令。
此外,当一个函数中定义了多个defer语句时,函数退出时将按照从后往前的顺序执行这些panic语句。我们再来看一个例子:
func testDefer() {
defer fmt.Println("testDefer 1")
var err MyError
defer fmt.Println("testDefer 2")
fmt.Println(err.Error())
defer fmt.Println("testDefer 3")
}
func main() {
testDefer()
}
我们在testDefer
方法中故意写了一个空指针异常,运行结果如下:
testDefer 2
testDefer 1
panic: runtime error: invalid memory address or nil pointer dereference
……
"testDefer 3"没有得到执行,因为在执行到fmt.Println(err.Error())
时就以及异常了,只会执行之前已经注册的defer语句。
三. panic
当程序遇到严重的系统问题,需要停止运行时,可以使用系统内置的panic
函数来报告错误。这通常可以应用在程序启动时执行初始化的阶段,因为一旦初始化失败,系统应当停止运行,否则错误初始化的程序将可能导致更多未知的错误。
当程序执行panic时,正常的执行流程将立即结束,但是函数中声明的defer语句仍将继续执行,之后将按照调用链条逐层向上执行panic流程。
一个简单的示例:
func print(s interface{}) {
// 获取s的类型并判断
switch sType := s.(type) {
case string:
fmt.Println(s.(string))
case int:
fmt.Println(s.(int))
default:
panic(fmt.Sprint("unknown type:", sType))
}
}
func main() {
print("hello")
print(10)
print(true)
}
运行结果如下:
hello
panic: unknown type:true
10
goroutine 1 [running]:
main.print(0x109d060, 0x1129721)
/Users/Gyz/peng/workspace/test/src/test/main.go:21 +0x212
main.main()
/Users/Gyz/peng/workspace/test/src/test/main.go:28 +0x7b
Process finished with exit code 2
四. recover
通常,在程序中遇到错误并执行了panic方法后,程序会在执行完defer语句后退出,但是有时我们并不希望程序退出,而是希望能捕获到异常信息,打印日志或者触发报警,但程序依然继续运行。recover
方法就是用于这个目的。recover
方法通常放在defer语句中,以保证一定会得到执行,如下:
func print(s interface{}) {
defer func() {
if r := recover(); r!= nil {
fmt.Println("catch exception:", r)
}
}()
switch sType := s.(type) {
case string:
fmt.Println(s.(string))
case int:
fmt.Println(s.(int))
default:
panic(fmt.Sprint("unknown type:", sType))
}
}
func main() {
print(true)
print("hello")
print(10)
}
我们在print
方法的最前面声明了defer
函数,并在其中用recover
方法捕获异常,这样,即使捕获到异常,也只是打印一条错误信息,不会导致程序退出。执行结果如下:
catch exception: unknown type:true
hello
10
五. 小结
以上,我们列举了Go语言在异常处理方面的一些特性,通过这些简单的测试代码,我们可以大致看出Go语言的异常处理方式:
通过内置的错误类型
error
来记录已识别的异常信息,并返回给调用函数通过
recover()
方法来捕捉未识别的异常信息,并结束异常,使程序继续运行通过
panic()
主动结束程序通过
defer
语句强制程序在退出前执行必要的清理
参考资料
许式伟:《Go语言编程》
以上是关于Go语言学习四——异常处理的主要内容,如果未能解决你的问题,请参考以下文章