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类型

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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