Go语言错误与异常处理机制

社区订阅号:Golang语言社区 社区服务号:Golang技术社区 如有问题或建议,请公众号留言 社区问答系统于5月1日内测,内测邀请码社区微店有售

出处:

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

1 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"
)
/**
 * 自定义Error类型,实现内建Error接口
 * type Error interface {
 *      Error() string
 * }
 */
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 0(产生错误的情况:

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

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

2 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)
}

3 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)
}

输入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\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类型")
            }
        }
    }()

因为在在陷入panic之前defer语句没有被加载到内存,而在执行panic时程序被中断,因而无法执行defer语句。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2018-04-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户画像

H5中的标记方法

要使用H5标记,必须先进行如下的doctype声明,不区分大小写。Web浏览器通过判断文件开头有没有这个声明,来判断解析器和渲染类型是否切换到对应的H5模式。

821
来自专栏Android开发指南

编码规范

3398
来自专栏大史住在大前端

javascript基础修炼(3)—What's this(下)

严格模式是ES5中添加的javascript的另一种运行模式,它可以禁止使用一些语法上不合理的部分,提高编译和运行速度,但语法要求也更为严格,使用use str...

1102
来自专栏漫漫前端路

巧用 TypeScript (一)

TypeScript 提供函数重载的功能,用来处理因函数参数不同而返回类型不同的使用场景,使用时,只需为同一个函数定义多个类型即可,简单使用如下所示:

2942
来自专栏python百例

118-ip地址与10进制数的转换

当我们ping数字2130706433时,从127.0.0.1返回结果。为什么是这样呢? IP地址是个32位的二进制数,表示成点分10进制,只是为了方便,如果...

1753
来自专栏菩提树下的杨过

[复习]The C Programming Language 2nd 习题集(1.1-1.10)

买不起iPhone4,只能弄了一台iTouch4,想尝试一下iOS上的开发,虽然有monoTouch可用,但是这东西要399美金授权,换成RMB好几千块了,算了...

2286
来自专栏狮乐园

高级 Angular 组件模式 (5)

在之前的例子中,已经出现多次使用template reference variable(模板引用变量)的场景,现在让我们来深入研究如何通过使用模板引用变量来关联...

922
来自专栏Laoqi's Linux运维专列

正则三剑客-sed

与grep不同的是,当使用sed匹配字符串的时候如下: #sed -n ‘//‘p file             // 内填写需要匹配的字符串 例如: #s...

3095
来自专栏编程心路

想学习php的,不如来这里看看

win+R打开命令行,cmd进DOS窗口 DOS命令开启关闭Apache和Mysql Apache启动关闭命令

1253
来自专栏IT派

全面深入理解Python面向对象编程

面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,即:将之前实现的代码块复制到现需功能处。

1222

扫码关注云+社区

领取腾讯云代金券