Skip to content

Latest commit

 

History

History
367 lines (280 loc) · 10.8 KB

7.Golang错误处理(七).md

File metadata and controls

367 lines (280 loc) · 10.8 KB

Golang错误处理(七)

Go编程提供了一个非常简单的错误处理框架,以及内置的错误接口类型,error类型实际上是抽象了Error()方法的error接口,Golang使用该接口进行标准的错误处理。error对应源代码如下:

type error interface {
   Error() string
}

函数通常返回错误作为最后一个返回值。可使用errors.New()来构造一个基本的错误消息,如下所示:

func Sqrt(value float64)(float64, error) {
   if(value < 0){
      return 0, errors.New("Math: negative number passed to Sqrt")
   }
   return math.Sqrt(value), nil
}

使用返回值和错误消息,如下所示:

result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

除了上面的errors.New用法之外,我们也可以实现error接口,自己实现Error()方法,来达到自定义参数的错误输出。示例代码如下:

package main

import (
 "fmt"
 "math"
)

type dualError struct {
 Num     float64
 problem string
}

func (e dualError) Error() string {
  return fmt.Sprintf("Wrong!!!,because \"%f\" is a negative number",e.Num)
}

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return -1, dualError{Num:f}
    }
     return math.Sqrt(f) , nil
}

func main(){
      result, err:= Sqrt(-13)
      if err != nil   {
        fmt.Println(err)
      }else{
        fmt.Println(result)
      }
}

Go语言没有像Java.NET那样的try/catch异常机制:不能执行抛异常操作。但是有一套defer-panic-and-recover机制。

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

panic介绍

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

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

panic()是一个内建函数,可以中断原有的控制流程,进入一个令人panic(恐慌即Java中的异常)的流程中。当函数调用panic时,函数的执行会被中断,最简单的panic示例代码如下:

package main

import "fmt"

func main() {
    fmt.Println("Starting the program")
    panic("A severe error occurred: stopping the program!")
    fmt.Println("Ending the program")
}

执行结果:

Starting the program
panic: A severe error occurred: stopping the program!

goroutine 1 [running]:
main.main()
    /Users/charon/go/src/main/Demo.go:7 +0x79

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

defer

Go引入关键字defer来延迟执行defer后面的函数。一直等到包含defer语句的函数执行完毕时,延迟函数(defer后的函数)才会被执行,而不管包含defer语句的函数是通过return的正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。 在Go语言中,可以使用关键字defer向函数注册退出调用,即主调函数退出时,defer后的函数才会被调用。

defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码(相当于Java中的finally)。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。所以defer常用语一些关闭文件流、关闭数据库等最后的资源清理工作。

在处理异常时候这个特点很有用处:有defer关键字之后,即便函数抛出了异常,也会被执行,这样就不会因程序出现了错误而导致资源不会释放了。

package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}

上面的执行结果是:

hello      
world

defer

延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。

package main

import "fmt"

func main() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}

它的执行结果是:

counting
done
9
8
7
6
5
4
3
2
1
0

如果有多个defer语句,哪怕某一个defer执行的调用发生错误,这些调用依旧都会被执行,因为defer是在当前所处的函数执行完成后才去执行的。

func test(x int) {
	fmt.Println(100 / x)
}

func main() {
	defer fmt.Println("aaaa")
	defer fmt.Println("bbbb")
	defer test(0)
	defer fmt.Println("cccc")
}

执行结果:

cccc
bbbb
aaaa
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.test(0x0)
	/Users/charon/go/src/awesomeProject/HelloWorld.go:6 +0x105
main.main()
	/Users/charon/go/src/awesomeProject/HelloWorld.go:14 +0x1f5

Debugger finished with exit code 0

defer与匿名函数结合使用

func main() {
    a := 10
    b := 20
    defer func (a, b int) {
        fmt.Println("a = ", a, ": b = ", b)
    }(a, b)

    a = 100
    b = 200

    fmt.Println("outer : a = ", a, ": b = ", b)
}

执行结果:

outer : a =  100 : b =  200
a =  10 : b =  20

哎?为啥打印出来的是10和20呢? defer是最后执行,而ab已经被赋值成100和200了,应该打印出100和200才对啊。

这是因为代码从上往下执行的时候:

a := 10
b := 20
// 代码执行到这里的时候a和b的值还都是10和20,这时候会把a和b的值当做参数传递进去,只是没有调用,所以等他调用的时候,其实之前传递过来的值分别是10和20
defer func (a, b int) {
    fmt.Println("a = ", a, ": b = ", b)
}(a, b)
// 这里虽然更改了a和b的值,但是在这之前已经把a和b的值传递过去了
a = 100
b = 200

fmt.Println("outer : a = ", a, ": b = ", b)

recover介绍

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

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

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

注意:上面说的goroutine是什么意思?Go语言里面的并发使用的是GoroutineGoroutine可以看做一种轻量级的线程,或者叫用户级线程。与JavaThread很像。

defer执行recover

defer的函数在return之后执行,这个时机点正好可以捕获函数抛出的panic,因而defer的另一个重要用途就是执行recover

recover只有在defer中使用才更有意义,如果在其他地方使用,由于程序已经调用结束而提前返回而无法有效捕捉错误。

一个展示panicdeferrecover怎么结合使用的完整例子如下:

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()方法,并结合panicrecover,集中展示下Go语言的错误处理机制:

package main

import (
    "fmt"
)

//自定义错误类型
type ArithmeticError struct {
    error
}

//重写Error()方法
func (this *ArithmeticError) Error() string {
    return "自定义的error,error名称为算数不合法"
}

//定义除法运算函数***这里与本文中第一幕①error接口的例子不同
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)
}

运行,输入ddd:

ddd
panic的内容自定义的error,error名称为算数不合法
panic--recover()得到的是error类型
panic--recover()得到的是ArithmeticError类型