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语句。
  • 相关阅读:
    说说 Java 线程间通信
    Java 内存模型与内存结构
    Spring Boot 整合 Shiro
    HashMap 实现原理
    Spring Boot 自动配置原理
    Spring Cloud 全链路追踪实现
    JVM 类加载机制
    volatile 关键字的作用
    Spring Boot 整合 Redis
    Docker命令
  • 原文地址:https://www.cnblogs.com/Mike-zh/p/3789664.html
Copyright © 2011-2022 走看看