Go入门17:错误处理之 Panic-Recover

Go 语言没有像Java那样的 try/catch 异常机制,它不能执行抛异常操作,而是使用了 panic 和 recover 机制。

Go语言中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。一直等到包含defer语句的函数执行完毕时,延迟函数(defer后的函数)才会被执行,而不管包含defer语句的函数是通过return的正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。

一定要记住,应当把 panic 和 recover 机制作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少 panic 的东西。这是一个强大的工具,请明智地使用它。

一、Panic

panic()是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌(即Java中的异常)的流程中。当函数F调用panic时,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。

恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。

panic 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 panic 函数产生一个中止程序的运行时错误。panic接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。

当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时panic,伴随着程序的崩溃抛出一个runtime.Error 接口类型的值。这个错误值有个RuntimeError() 方法用于区别普通错误。

最简单的panic示例代码如下:

func main() {

    fmt.Println("Starting the program")

    panic("A severe error occurred: stopping the program!")

    fmt.Println("Ending the program")

}

这段代码的打印结果因人而异,在win10 64位+go1.7.5的环境下打印结果如下:

Starting the program

panic: A severe error occurred: stopping the program!

goroutine 1 [running]:

panic(0x4951e0, 0xc0420062c0)

        D:/Go/src/runtime/panic.go:500 +0x1af

main.main()

        E:/testcode/test.go:7 +0xf6

exit status 2

var user = os.Getenv("USER")

func init() {

    if user == "" {

        panic("no value for $USER")

    }

}

二、Recover

recover是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入panic,调用recover可以捕获到panic的输入值,并且恢复正常的执行。

一般情况下, recover()应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程(使用recover关键字),会导致该goroutine所属的进程打印异常信息后直接退出。

从程序栈的角度来讲,panic会导致栈被展开直到defer修饰的 recover() 被调用或者程序中止。

func throwsPanic(f func()) (b bool) {

    defer func() {

        if x := recover(); x!=nil {

            b = true

        }

    }()

    f()    // 执行函数f,如果f中出现了panic,那么就可以恢复回来

    return

}

三、综合示例

一个展示panic,defer和 recover怎么结合使用的完整例子如下:

package main

import (

    "fmt"

)

func badCall() {

    panic("bad end")

}

func test() {

    defer func() {

        if e := recover(); e != nil {

            fmt.Printf("Panicing %s\n", e)

        }

    }()

    badCall()

    fmt.Printf("After bad call\n")

}

func main() {

    fmt.Printf("Calling test\n")

    test()

    fmt.Printf("Test completed\n")

}

打印输出:

Calling test

Panicing bad end

Test completed

四、错误处理机制总结

Go 语言的错误处理流程:当一个函数在执行过程中出现了异常或遇到 panic(),正常语句就会立即终止,然后执行defer语句,再报告异常信息,最后退出 goroutine。如果在defer中使用了 recover()函数,则会捕获错误信息,使该错误信息终止报告。

以下实现一个自定义的Error()方法,并结合panic和recover,集中展示下Go语言的错误处理机制:

package main

import (

    "fmt"

)

//自定义错误类型

type ArithmeticError struct {

    error

}

//重写Error()方法

func (this *ArithmeticError) Error() string {

    return "自定义的error,error名称为算数不合法"

}

//定义除法运算函数Devide

func Devide(num1, num2 int) int {

    if num2 == 0 {

        panic(&ArithmeticError{}) //当然也可以使用ArithmeticError{}同时recover等到ArithmeticError类型

    } else {

        return num1 / num2

    }

}

func main() {

    var a, b int

    fmt.Scanf("%d %d", &a, &b)

    defer func() {

        if r := recover(); r != nil {

            fmt.Printf("panic的内容:%v\n", r)

            if _, ok := r.(error); ok {

                fmt.Println("panic--recover()得到的是error类型")

            }

            if _, ok := r.(*ArithmeticError); ok {

                fmt.Println("panic--recover()得到的是ArithmeticError类型")

            }

            if _, ok := r.(string); ok {

                fmt.Println("panic--recover()得到的是string类型")

            }

        }

    }()

    rs := Devide(a, b)

    fmt.Println("结果是:", rs)

}

终端执行代码,打印输出:

$ go run test.go

5 2

结果是: 2

$ go run test.go

5 0

panic的内容:自定义的error,error名称为算数不合法

panic--recover()得到的是error类型

panic--recover()得到的是ArithmeticError类型

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容

  • 01.{ 换行: Opening Brace Can't Be Placed on a Separate Lin...
    码农不器阅读 2,401评论 0 14
  • 选择题 [primary] 下面属于关键字的是() A. func B. def C. struct D. cla...
    盘木阅读 1,833评论 0 29
  • Go入门 Go介绍 部落图鉴之Go:爹好还这么努力? 环境配置 安装 下载源码编译安装 下载相应平台的安装包安装 ...
    齐天大圣李圣杰阅读 4,592评论 0 26
  • 将两个(或更多)语句放在一行书写,它们 必须用分号 (’;’) 分隔。一般情况下,你不需要分号。 init函数和m...
    涵仔睡觉阅读 3,789评论 0 8
  • 为什么你总是可以给我一种感觉,没别人的时候捧着,有别人的时候就是一团空气
    81ca1b7bae8e阅读 91评论 0 0