go语言之抛出异常

2019/05/03 17:40
阅读数 15

一: panic和recover 

作用:panic 用来主动抛出错误; recover 用来捕获 panic 抛出的错误。

概述: 1,引发panic有两种情况 1)程序主动调用panic函数 2)程序产生运行时错误,由运行时检测并抛出 过程: ! 发生 panic 后,程序会从调用 panic的函数位置或发生panic 的地方立即返回,逐层向上执行函数的defer语句, 然后逐层打印函数调用堆栈,直到被 recover 捕获或运行到最外层函数而退出。 ! panic的参数是一个空接口类型 interface{},所以任意类型的变量都可以传递给 panic(xxx) ! panic 不但可以在函数正常流程中抛出,在 defer 逻辑里也可以再次调用 panic 或抛出 panic a defer 里面的 panic 能够被后续执行的 defer 捕获。 recover()用来捕获 panic,阻止panic继续向上传递recover()和defer一起使用 ,但是recover() 只  有在defer后面的函数体内被直接调用才能捕获panic终止异常否则返回 nil,异常继续向外传递

//这会获取失败
defer recover()
defer fmt.println(recover())
//嵌套两层也会获取失败
defer func() {
	func(){
		println("defer inner")
		recover() //无效
	}()
}

  以下场景会获取成功

package main
func f(){
    defer func() {
        println("defer inner")
        recover()
    }()
}
func except()  {
    recover()
}
func test(){
    defer except()
    panic("test panic")
}
func main()  {
    f()
    except()
    test()
}

  可以有多个panic被抛出,连续多个panic的场景只能出现在延迟调用里面,否则不会出现多个panic被抛出的场景。但只有最后一次panic能被捕获

package main

import "fmt"

func main(){
    defer func() {
        if err :=recover();err !=nil{
            fmt.Println(err)
        }
    }()
  //只有最后一次panic调用被捕获
    defer func() {
        panic("first defer panic")   //打印结构是这个
    }()
    defer func() {
        panic("second defer panic")
    }()
    panic("main body panic")
}

  包中 in it 函数引发的 panic 只能在 in it 函数中捕获,在 main 中无法被捕获,原因是 in it
数先于 main 执行,函数并不能捕获内部新启动的 goroutine 所抛出的 panic 。

package main

import (
    "fmt"
    "time"
)

func do()  {
    //这里并不能获取da函数中的panic
    defer func() {
        if err := recover();err != nil{
            fmt.Println(err)
        }
    }()
    go da()
    go db()
    time.Sleep(3*time.Second)
}
func da(){
    panic("panic da")
    for i := 0; i<10; i++{
        fmt.Println(i)
    }
}
func db(){
    for i := 0; i<10; i++{
        fmt.Println(i)
    }
}
func main()  {
    fmt.Println(do)
    fmt.Println(da)
    fmt.Println(db)
}

  示例

package main

import "fmt"

//系统抛异常
func test01() {
    a := [5]int{0, 1, 2, 3, 4}
    //a[10] = 123
    index := 10
    a[index] = 123
}
func main() {
    //panic: runtime error: index out of range
    //test01()
    //test02()
    //test03()
    test04()
}
//自己抛异常
func test02() {
    getCircleArea(-5)
}
func getCircleArea(radius float32) (area float32) {
    if radius < 0 {
        //抛异常
        panic("半径不能为负数")
    }
    return 3.14 * radius * radius
}
func test03() {
    //延时执行匿名函数
    //延时到什么时候?要么正常结束,要么出异常
    //recover()是复活的意思
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    //会报错
    getCircleArea(-5)
    //下句话没有打印
    fmt.Println("这里有没有执行?")
}
func test04()  {
    test03()
    fmt.Println("GAME OVER")
}

返回异常

package main

import (
   "errors"
   "fmt"
)

//算半径
func getCircleArea(radius float32) (ret float32, err error) {
   if radius < 0 {
      //创建异常
      err = errors.New("沙雕,半径不能为负数")
      return
   }
   ret = 3.14 * radius * radius
   return
}

func main() {
   ret, err := getCircleArea(5)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println("ret=", ret)
   }
}

使用场景:

1)程序遇到了无法正常执行下去的错误,主动调用 panic 函数结束程序运行
(2)在调试程序时,通过主动调用 panic 实现快速退出, panic 打印出的堆枝能够更快地定位错误。

为了保证程序的健壮性,需要主动在程序的分支流程上使用 recover()拦截运行时错误。

Go 提供了两种处理错误 方式,
一 种是借助 panic和 recover 的抛出捕获机制,
另一种使用error 错误类型

error:

go 语言内置错误接口类型
任何类型只要实现 error() string 方法,都可以传递 eηor接口类型变量。
Go 语言典型的错误处理方式是将error作为函数最后一个返回值 在调用函数 通过检测其返回的error值是否为nil来进行错误处理。
type  error interface{
        Error()  string
}

  

• 在多个返回值的函数中,error 通常作为函数最后一个返回值

• 如果一个函数返回error 类型变量 ,则先用if语句处理 error != nil 异常场景,正常逻辑放到 if 语句块的后面,
保持代码平坦。
• defer 吾句应该放到四判断的后面,不然有可能产生 panic
• 在错误逐级向上传递的过程中,错误信息应该不断地丰富和完善,而不是简单地抛出下层调用的错误。
这在错误日志分析 非常有用和友好。

错误和异常

广义上的错误: 发生非期望的行为。
狭义的错误:发生非期望的己知行为,这里的己知是指错误的类型是预料并定义好的。
异常: 发生非期待的未知行为。这里的未知是指错误的类型不在预先定义的范围内。异常又被称为未捕获的错误
( untrapped error )。程序在执行时发生未预先定义的错误,程序编译器和运行时都没有及时将其捕获处理。
而是由操作系统进行异常处理,比如 语言程序里面经常出现的 Segmentation Fault (段异常错误),这个就属
于异常范畴。

Go 是一门类型安全的语言,其运行时不 出现这种编译器和运行时都无法捕获的错 ,也就是说,
不会出现 untrapped error ,所以从这个角度来说, Go 语言不存在所谓的异常,出现的“异常”全是错误

Go 程序需要处理的这些错误可 分为两类
1   一类是运行时错误( runtime errors ),此类错误语言的运行时能够捕获,并采取措施一一隐式或显式地抛出 panic
2  一类是程序逻辑错误:程序执行结果不符合预期,但不会引发运行时错误
对于运行时错误程序员无法完全避免其发生,只能尽量减少其发生的概率,并在不影响程序主功能的分支流程上
“ rcover ”这些 panic,避免其因为一个panic引发整个程序的崩溃。

Go 对于错误提供了两种处理机制:

(1) 通过函数返回错误类型的值来处理错误。
(2) 通过 panic 打印程序调用枝,终止程序执行来处理错误。
所以对错误的处理也有两种方法,
    一种是通过返回 个错误类型值来处理错误,
    另一种是直接调用 panic 抛出错误,退出程序。
Go 是静态强类型语言,程序的大部分错误是可以在编译器检测到的,但是有些错误行为需要在运行期才能检测出来。
此种错误行为将导致程序异常退出 。其表现出的行为就和直接调用panic 一样 打印出函数调用技信息,并且终止程序执行
在实际的编程中,error和panic 的使用应该遵循如下三条原则:
1)程序局部代码的执行结果不符合预期,但此种行为不是运行时错误范围内预定义的错
误,此种非期望的行为不会导致程序无法提供服务,此类场景应该使用函数返回 rror 类型变量
进行错误处理。
2)程序执行过程中发生错误,且该种错误是运行时错误范围内预定义的错误,此时 Go
语言默认的隐式处理动作就是调用 panic ,如果此种 panic 发生在程序的分支流程不影响主要更
能,则可以在发生 panic 的程序分支上游处使用 recove 进行捕获,避免引发整个程序的崩溃。
3)程序局部代码执行结果不符合预期,此种行为虽然不是运行时错误范围内预定义的错
误,但此种非期望的行为会导致程序无法继续提供服务,此类场景在代码中应该主动调用 panic
终止程序的执行。
进一步浓缩为两条规则
(1)程序发生的错误导致程序不能容错继续执行,此时程序应该主动调用 panic 或由运行
时抛出 panic
(2)程序虽然发生错误 但是程序能够容错继续执行,此时应该使用错误返回值的方式处理错误,
或者在可能发生运行时错误的非关键分支上使用 recover 捕获 panic

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部