go学习(十二)——Go 语言错误与异常处理机制

  • 错误处理
  • error接口
  • defer--延迟语句
    • 例子1:defer语句加载时记录值
    • 例子2:在函数返回后执行
    • 例子3:defer语句会读取主调函数的返回值,并对返回值赋值.(注意和例子2的区别)
  • panic-recover运行时异常处理机制
    • panic()
    • recover()
写在前面: 
近来关于对Golang的讨论有很多,七牛的几个大牛们也断定Go语言在未来将会快速发展,这里的内容大多来自七牛团队的《Go语言编程》一书。这里也会按照书中组织的章节进行学习。本篇主要介绍Go语言的错误与异常处理机制。

错误处理

在传统的OOP编程中,为了捕获程序的健壮性需要捕获异常,使用的方法大都是try() catch{}模块,例如, 在下面的java代码中,可能需要的操作是:

Connection conn = ...;
try {
    Statement stmt = ...;
    ...//别的一些异常捕获
finally {
    stmt.close();
    }
finally {
    conn.close(); 
}

而在Go中引入了三个关键字,分别是 defer、panic和recover,其中使用defer关键字语句的含义是不管程序是否出现异常,均在函数退出时自动执行相关代码。 
所以上面你的java代码用Go进程重写只有两行:

conn := ...
defer conn.Close()

另外两个关键词后面再讨论。所以“Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。这对于代码的阅读者和维护者来说也是一件很好的事情,因为可以避免在层层的代码嵌套中定位业务代码。”

error接口

Go语言中的error类型实际上是抽象了Error()方法的error接口

type error interface { Error() string }

Go语言使用该接口进行标准的错误处理。

对于大多数函数,如果要返回错误,大致上都可以定义为如下模式,将error作为多种返回
值中的最后一个,但这并非是强制要求:

func Foo(param int)(n int, err error) { // ...
}

调用时的代码建议按如下方式处理错误情况:

复制代码
n, err := Foo(0) if err != nil { // 错误处理
} else { // 使用返回值n
}
复制代码

看下面的例子综合了一下error接口的用法:

运行,输入参数7 3(正确的情况):

若输入7 0(产生错误的情况):

通过上面的例子可以看出error类型类似于Java中的Exception类型,不同的是Exception必须搭配throw和catch使用。

defer--延迟语句

在Go语言中,可以使用关键字defer向函数注册退出调用,即主调函数退出时,defer后的函数才会被调用。
defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码。(相当于Java中的finally )

当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。

例如:

其执行结果为:

defer语句在声明时被加载到内存(多个defer语句按照FIFO原则) ,加载时记录变量的值,而在函数返回之后执行,看下面的例子:

例子1:defer语句加载时记录值

其结果显然是0

例子2:在函数返回后执行


结果是:

例子3:defer语句会读取主调函数的返回值,并对返回值赋值.(注意和例子2的区别)

其结果竟然是2.

通过上面的几个例子,自然而然会想到用defer语句做清理工作,释放内存资源(这样你再也不会为Java中的try-catch-finally层层嵌套而苦恼了)

例如关闭文件句柄:

srcFile,err := os.Open("myFile") 
defer srcFile.Close()

关闭互斥锁:

mutex.Lock() 
defer mutex.Unlock()

上面例子中defer语句的用法有两个优点:

1.让设计者永远也不会忘记关闭文件,有时当函数返回时常常忘记释放打开的资源变量。

2.将关闭和打开靠在一起,程序的意图变得清晰很多。

下面看一个文件复制的例子:

package main


import (

    "fmt"

    "io"

    "os"

)


func main() {

    copylen, err := copyFile("dst.txt", "src.txt")

    if err != nil {

        return

    } else {

        fmt.Println(copylen)

    }


}


//函数copyFile的功能是将源文件sec的数据复制给dst

func copyFile(dstName, srcName string) (copylen int64, err error) {

    src, err := os.Open(srcName)

    if err != nil {

        return

    }

    //当return时就会调用src.Close()把源文件关闭

    defer src.Close()

    dst, err := os.Create(dstName)

    if err != nil {

        return

    }

    //当return是就会调用src.Close()把目标文件关闭

    defer dst.Close()

    return io.Copy(dst, src)

}


可以看到确实比Java简洁许多。

panic-recover运行时异常处理机制

Go语言中没有Java中那种try-catch-finally结构化异常处理机制,而使用panic()函数代替throw/raise引发错误,

然后在defer语句中调用recover()函数捕获错误,这就是Go语言的异常恢复机制——panic-recover机制

两个函数的原型为:

func panic(interface{})//接受任意类型参数 无返回值
func recover() interface{}//可以返回任意类型 无参数

一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢?

panic()

是一个内建函数,可以中断原有的控制流程,进入一个令人panic(恐慌即Java中的异常)的流程中。当函数F调用panic,函数F的执行被中
断,但是F中的延迟函数(必须是在panic之前的已加载的defer)会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一
过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。异常可以直接调用panic产
生。也可以由运行时错误产生,例如访问越界的数组。

recover()

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

一般情况下,recover()应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复

过程(使用recover关键字),会导致该goroutine所属的进程打印异常信息后直接退出。

这里结合自定义的error类型给出一个使用panic和recover的完整例子:

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)

}

其执行的结果为:

使用与上面相同的测试数据,输入9 5得:

输入9 5得:


参考链接:https://studygolang.com/articles/2959

                  http://www.cnblogs.com/Mike-zh/p/3789664.html

你可能感兴趣的:(Go入门教程)