zoukankan      html  css  js  c++  java
  • 【Go语言】错误与异常处理机制

    ①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接口的用法:

    package main
    
    import (
        "fmt"
    )
    
    //自定义错误类型
    type ArithmeticError struct {
        error   //实现error接口
    }
    
    //重写Error()方法
    func (this *ArithmeticError) Error() string {
        return "自定义的error,error名称为算数不合法"
    }
    
    //定义除法运算函数
    func Devide(num1, num2 int) (rs int, err error) {
        if num2 == 0 {
            return 0, &ArithmeticError{}
        } else {
            return num1 / num2, nil
        }
    }
    func main() {
        var a, b int
        fmt.Scanf("%d %d", &a, &b)
    
        rs, err := Devide(a, b)
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Println("结果是:", rs)
        }
    }

    运行,输入参数5 2(正确的情况):

    5 2
    结果是: 2

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

    5 0
    自定义的error,error名称为算数不合法

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

    ②defer--延迟语句

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

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

    例如:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        for i := 0; i < 5; i++ {
            defer fmt.Println(i)
        }
    }

    其执行结果为:

    4
    3
    2
    1
    0

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

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

    func f1() {
        i := 0
        defer fmt.Println(i) //实际上是将fmt.Println(0)加载到内存
        i++
        return
    }
    func main() {
        f1()
    }

    其结果显然是0

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

    func f2() (i int) {
        var a int = 1
        defer func() {
            a++
            fmt.Println("defer内部", a)
        }()
        return a
    }
    func main() {
        fmt.Println("main中", f2())
    }

    其结果是

    defer内部 2
    main中 1

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

    func f3() (i int) {
        defer func() {
            i++
        }()
        return 1
    }
    func main() {
        fmt.Println(f3())
    }

    其结果竟然是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
    ", 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)
    }

    其执行的结果为:

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

    5 2
    结果是: 2

    输入5 0得:

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

    可见已将error示例程序转换为了Java中的用法,但是在大多数程序中使用error处理的方法较多。

    需要注意的是:defer语句定义的位置 如果defer放在了

     rs := Devide(a, b)语句之后,defer将没有机会执行即下面的程序失效

    rs := Devide(a, b)
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("panic的内容%v
    ", 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类型")
                }
            }
        }()
    因为在在陷入panic之前defer语句没有被加载到内存,而在执行panic时程序被中断,因而无法执行defer语句。
  • 相关阅读:
    Siege 3.0 正式版发布,压力测试工具
    Pomm 1.1.2 发布,专为 PG 设计的 ORM 框架
    Whonix 0.5.6 发布,匿名通用操作系统
    国内开源 java cms,Jspxcms 2.0 发布
    EZNamespaceExtensions.Net v2013增加对上下文菜单、缩略图、图标、属性表的支持
    GNU Guile 2.0.9 发布,Scheme 实现
    jdao 1.0.4 发布 轻量级的orm工具包
    OpenSearchServer 1.4 RC4 发布
    Percona Server for MySQL 5.5.3030.2
    Samba 4.0.5 发布
  • 原文地址:https://www.cnblogs.com/Mike-zh/p/3789664.html
Copyright © 2011-2022 走看看