异常处理1:恐慌与处理
恐慌概述
- 代码在运行时如果出现异常,系统会报出恐慌(panic)并终止运行
- IDE和终端打印的恐慌日志,包含了恐慌的信息以及报恐慌所在的代码行
- 恐慌好比一种震撼的暴力教育,其目的在于警示开发者,什么是可以的而什么又是不可以的
- 代码在交付使用前要经过充分测试,处理一切可能的恐慌
系统报恐慌
本例中由于我们错误地使用了一个超出数组长度的下标,导致系统报出恐慌
func demo21() {
a := [5]int{0, 1, 2, 3, 4}
a[1] = 123
index := 2 + 8
//系统报恐慌:运行时错误(exe在执行的过程中发生的错误),下标越界
//panic: runtime error: index out of range
a[index] = 123
}
自己报恐慌
- 除了系统报出恐慌以外,我们还可以通过内建函数panic自己报出恐慌
- 自己报恐慌的目的,是预测程序在运行时可能出现的异常情形,并提示当前代码的调用者以错误信息
- 下面的例子中,计算圆面积这一函数在调用时如果传入了一个负数的半径,则会报出恐慌,提示半径是不可以为负数的,这样做相当于强制代码的调用者传入非负的半径
func getCircleArea(radius float32) (area float32) {
//如果半径参数为负数,则抛出异常
if radius < 0{
panic("颤抖吧,您的智商已下线,半径不能为负数")
}
return 3.14 * radius * radius
}
func demo22() {
//程序会因为抛出的异常未经处理而崩溃
getCircleArea(-5)
}
处理恐慌
- 程序在上线前必须测试和扫灭所有可能的恐慌
- 在没有对恐慌进行任何处理前,程序会在报恐慌的行崩溃
- Go语言提供了recover內建函数,让崩溃的程序复活并返回造成程序崩溃的error实例
- 所以我们可以在正式的业务逻辑开始前,事先挂起一个延时处理恐慌的函数,在其中借助recover函数获得造成程序崩溃的error并处理
- 当程序在123行崩溃时,123行以后的代码就不会再执行了,而是直接跳转到defer了的恐慌处理程序
func demo23() {
//延时执行恐慌处理程序
//延时到什么时候?①函数正常结束前②恐慌发生时(函数内恐慌以后的代码将不会执行)
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
//下面的函数会恐慌(报恐慌)
getCircleArea(-5)
//所以这里执行不到,而是直接跳到defer所定义的恐慌处理程序
print("这里有美女相赠")
}
- 下面这段代码对前面的demo23进行了调用
- 由于demo23()是一个已经定义了恐慌处理方式的函数,程序不会因为demo23中的恐慌而崩溃
func demo24() {
//本来该应该因恐慌而死,但已经在其中插入了异常(恐慌)处理程序,就不会造成程序崩溃
//丢失的部分仅仅为demo23中恐慌以后的部分
demo23()
fmt.Println("抢钱抢粮抢地盘")
fmt.Println("GAME OVER")
}
异常处理2:以返回错误替代恐慌
概述
- 通过恐慌报错的方式虽然直白有效,但动不动就崩溃显得有些暴力
- Go语言还给我们提供了一种相对温和但同样有效的异常解决方案,那就是同时返回结果和错误(error实例)
- 如果结果正确时错误为空,如果错误不为空时结果为空(或没有业务意义的默认值)
- 这种方式显得温和而辩证,兼容性好,也不会造成程序崩溃
- 至于究竟是严厉好还是温和兼容好,开发者们可以见仁见智
下面实例中的圆面积计算函数中:
- 如果调用者传入了一个负数半径,程序也不会panic,但是会返回一个提示错误的error实例,此时结果是毫无意义的默认值0
- 如果调用者传入了合法的半径,则返回正确结果和一个为空的error实例
package main
import (
"errors"
"fmt"
)
func main() {
//获得调用的结果
ret, err := getCircleAreaII(5)
//如果返回的错误不为空,则说明程序出现了异常
if err!=nil{
fmt.Println(err)
}else {
fmt.Println("ret=",ret)
}
}
/*
如果发生异常时,以返回错误的方式代替恐慌
正确结果和错误,必有一个为空
这样相对温和,也不会造成程序崩溃
*/
func getCircleAreaII(radius float32)(ret float32,err error) {
if radius < 0{
err = errors.New("傻鸟,半径不能为负")
return
}
ret = 3.14 * radius * radius
return
}
异常处理3:自定义异常
自定义异常概述
- 系统提供的异常类error无所不包但控制的粒度很粗糙
- 如果想要进行很精细化的报错和处理错误,就需要用到自定义异常
- 我们只要实现error接口中的Error() string方法,就可以自定义异常了
- 自定义的异常与系统异常无二,同样可以panic,同样可以辩证互斥地返回【结果错误对】
自定义异常
- 下面的例子定义了一个【非法参数异常】
- 在这个自定义的结构体中,封装了异常提示信息、发生时间、造成异常的用户三个属性
- 这样的异常在报出和处理时,无疑具有更直观更丰富的参考价值
//自定义【非法参数异常】,封装异常提示信息、发生时间、造成异常的用户三个属性
type InvalidArgError struct {
info string
when time.Time
user string
}
//实现系统的异常接口
func (iae *InvalidArgError)Error() string{
return fmt.Sprintln(iae.info,iae.when,iae.user)
}
//模仿SDK,提供一个创建异常对象的工厂方法,自动记录发生异常的时间和用户
func NewInvalidArgError(info string) *InvalidArgError {
iaePtr := new(InvalidArgError)
iaePtr.info = info
iaePtr.when = time.Now()
iaePtr.user = "bill"
return iaePtr
}
以错误的形式返回异常信息
//如果参数为负数时,返回一个自定义的异常
func getCircleAreaIII(radius float32)(ret float32,err interface{}) {
if radius < 0{
//err = errors.New("傻鸟,半径不能为负")
err = NewInvalidArgError("傻鸟,半径不能为负")
return
}
ret = 3.14 * radius * radius
return
}
//返回自定义异常
func demo41() {
ret, err := getCircleAreaIII(-5)
if err != nil {
//fmt.Println(err)
fmt.Sprintln(err)
} else {
fmt.Println("ret=", ret)
}
}
func main() {
demo41()
}
以恐慌的形式报异常并处理
//半径为负数的情况下以恐慌形式报出异常
func getCircleAreaIV(radius float32)(ret float32) {
if radius < 0{
panic(NewInvalidArgError("傻吊半径不能为负数"))
}
ret = 3.14 * radius * radius
return
}
//处理抛出的恐慌
func demo42() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
getCircleAreaIV(-5)
}
func main() {
demo42()
}