11.异常处理
11.异常处理
在大部分的编程语言都支持异常处理机制,例如比较常见的try...catch...finally。但Go语言的设计者认为其他语言的异常处理太过消耗资源,且设计和处理比较复杂。导致使用者不能很好的处理错误,甚至觉得处理麻烦,从而忽视或忽略错误,导致程序崩溃。为了解决这些问题,Go将错误处理设计的非常简单,如下所示:
函数调用时,允许返回多会个值,且最后一个返回值可以是error接口类型的值
- 如果调用产生错误,则这个值是一个error接口类型的错误
- 如果调用成功,则该值为nil
通过检查函数的返回值,如果为nil,则代表函数处理成功,否则则进行异常处理
11.1 error
error是Go中声明的接口类型,其定义如下所示:
type error interface {
Error() string
}
所有实现了
Error() string的方法,都可以使用Error()方法返回错误的具体描述。
除以上方法,还可以实现自定义error,可使用error包中的New方法返回一个error接口类型的错误实例(errors.New("This is error")) ,示例代码如下所示:
package main
import (
"errors"
"fmt"
)
type ErrorSamle struct {
e string
}
func (e *ErrorSamle) Error() string {
return e.e
}
// 因为ErrorSample已经实现了接口error的方法,因此这里可以使用构造函数NewErrorSample返回Error接口的一个实例
func NewErrorSample(e string) error {
return &ErrorSamle{e: e}
}
var ErrorDivsionByZero = errors.New("divsion is zero")
func Div(a, b float32) (float32, error) {
if b == 0 {
return 0.0, ErrorDivsionByZero
}
return a / b, nil
}
func main() {
if v, err := Div(1, 0); err == nil {
fmt.Printf("计算成功,值为%v\n", v)
} else {
fmt.Printf("计算错误,错误为:%v\n", err)
}
err:=NewErrorSample("自定义错误")
fmt.Printf("自定义错误 - %v\n",err)
}
运行结果如下所示:
计算错误,错误为:divsion is zero
自定义错误 - 自定义错误
11.2 panic
panic有时也称之为宕机。因为当其发生时,往往会造成程序崩溃、服务终止等后果。但如果在错误发生时,不及时panic而终止程序运行时,可能会生成更大的损失,因此及时终止程序,可以做到及时止损。因为panic的主要用例是让开发人员主动抛出异常,使程序进入宕机状态而终止运行。其产生的主要原因如下所示:
- runtime运行时错误导致抛出panic。例如数组越界、内存溢出等
- 主动抛出panic
panic的基本语法如下所示:
func panic(v any)
panci就一个参数 v,类型为空接口,传入为异常信息
示例代码如下所示:
package main
import "fmt"
func Div1(a, b float32) float32 {
if b == 0 {
panic("除数为0")
}
return a / b
}
func Div2(a, b float32) float32 {
// 这里除法,没有判断除数为0的场景,可能会出现panic
return a / b
}
func main() {
fmt.Println(Div1(5, 0))
fmt.Println(Div2(5, 0))
}
代码运行结果如下所示:
panic: 除数为0
goroutine 1 [running]:
main.Div1(0x40a00000, 0x0)
c:/Users/Surpass/Documents/GolangProjets/src/go-learning-note/02-代码/11/1102-panic/main.go:7 +0x4f
main.main()
c:/Users/Surpass/Documents/GolangProjets/src/go-learning-note/02-代码/11/1102-panic/main.go:18 +0x2a
Process 29488 has exited with status 2
11.3 defer
defer 本意推迟或延迟的意思,在Go语言则是起到延迟执行的作用。其语法也在学简单,在正常的语句前添加defer就可了。在函数中使用defer语句,会使得defer后跟的语句进行延迟处理,当该函数即将返回或panic,defer语句开始执行。
同一个函数可以拥有多个defer语句,依次加入调用栈(LIFO),函数返回或panic时,从栈顶依次执行defer后面的语句。执行的先后顺序和注册的顺序刚好相反(先注册的后执行)。
- 示例代码-1:
package main
import "fmt"
func PrintNumer(number int) {
fmt.Printf("当前数值为:%d\n", number)
}
func main() {
fmt.Println("函数开始运行")
defer PrintNumer(1)
defer PrintNumer(2)
defer PrintNumer(3)
defer PrintNumer(4)
fmt.Println("函数结束运行")
}
代码运行结果如下所示:
函数开始运行
函数结束运行
当前数值为:4
当前数值为:3
当前数值为:2
当前数值为:1
- 示例代码-2
package main
import "fmt"
func PrintNumer(number int) {
fmt.Printf("当前数值为:%d\n", number)
}
func main() {
number := 1
fmt.Println("函数开始运行")
defer PrintNumer(number)
number++
defer PrintNumer(number)
defer PrintNumer(number)
number++
defer PrintNumer(number)
fmt.Println("函数结束运行")
}
代码运行结果如下所示:
函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
当前数值为:2
当前数值为:1
- 示例代码-3
package main
import "fmt"
func PrintNumer(number int) {
fmt.Printf("当前数值为:%d\n", number)
}
func main() {
number := 1
fmt.Println("函数开始运行")
defer func(number int) { fmt.Printf("defer 延迟打印%d", number) }(number)
number++
defer PrintNumer(number)
defer PrintNumer(number)
number++
defer PrintNumer(number)
fmt.Println("函数结束运行")
}
代码运行结果如下所示:
函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
当前数值为:2
defer 延迟打印1
- 示例代码-4
package main
import "fmt"
func PrintNumer(number int) {
fmt.Printf("当前数值为:%d\n", number)
}
func main() {
number := 1
fmt.Println("函数开始运行")
defer func() { fmt.Printf("defer 延迟打印%d", number) }()
number++
defer PrintNumer(number)
defer PrintNumer(number)
number++
defer PrintNumer(number)
fmt.Println("函数结束运行")
}
代码运行结果如下所示:
函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
当前数值为:2
defer 延迟打印3
- 示例代码-5
package main
import "fmt"
var number int = 10
func PrintNumer(number int) {
fmt.Printf("当前数值为:%d\n", number)
}
func PrintNumer2() {
fmt.Printf("number 的值为:%d\n", number)
}
func main() {
number := 1
fmt.Println("函数开始运行")
defer func() { fmt.Printf("defer number is:%d\n", number) }() // 3
defer PrintNumer(number) // 1
defer PrintNumer2() // 注意变量作用域 10
number++
defer PrintNumer(number) // 2
number++
defer PrintNumer(number) // 3
fmt.Println("函数结束运行")
}
代码运行结果如下所示:
函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
number 的值为:10
当前数值为:1
defer number is:3
通过以上示例代码,总结如下所示:
- 如果函数时中存在defer,则在函数即将返回倒序执行defer后面的语句
- 如果defer中语句含有变量时,则其值在注册时计算
- 如果defer中语句为匿名函数且无参数时,没有办法准备参数。延迟执行时,需要重新计算函数语句块的变量
- 如果defer中语句为匿名函数且有参数时,可以准备参数,即当时注册时的变量值
- 一个函数可以拥有多个defer
defer在日常开发中,应用比较多,例如打开文件、连接数据库、sync同步等待等
11.4 recover
在使用panic抛出异常后,程序会停止执行,如果想让程序在异常情况下他能继续执行,可以将defer和recover结合起来,实现异常捕捉和恢复。类似于其他编程语言中的try-catch机制。
recover是内置函数,返回值为空接口,代表程序的异常信息,必须与defer一起使用才能实现异常的捕捉和处理。示例代码如下所示:
package main
import (
"errors"
"fmt"
)
var ErrorDivsionByZero = errors.New("除数为0")
func Div(a, b float32) float32 {
// 定义执行延迟执行的匿名函数
defer func() {
if err := recover(); err != nil {
// 异常不为空,主动抛出异常
fmt.Printf("捕捉到异常:%v", err)
} else {
fmt.Println("程序无异常")
}
}()
if b == 0 {
panic(ErrorDivsionByZero)
}
return a / b
}
func main() {
// 正常情况
Div(10, 5)
// 异常情况
Div(10, 0)
}
运行结果如下所示:
程序无异常
捕捉到异常:除数为0
在上面代码中,一旦在函数中发现panic,当前函数panic之后的语句不再执行,开始执行defer。如果在defer中错误被recover后,就相当于当前函数产生的错误得到了处理。当前函数执行完defer后退出当前函数,程序还可以从当前函数之后继续执行。在结合defer和recover捕捉和处理异常时,具备以下特点:
- 有panic,但没有recover,就没有地方处理错误,程序会出现崩溃
- 有panic,也有recover来捕捉时,则相当于错误被处理。当前defer执行完之后,退出当前函数,继续执行后续函数。
本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

作者: Surpassme
来源: http://www.jianshu.com/u/28161b7c9995/
http://www.cnblogs.com/surpassme/
声明:本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 ,否则保留追究法律责任的权利。如有问题,可发送邮件 联系。让我们尊重原创者版权,共同营造良好的IT朋友圈。

浙公网安备 33010602011771号