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
中引入两个内置函数panic
和recover
来触发和终止异常处理流程,同时引入关键字defer
来延迟执行defer
后面的函数。一直等到包含defer
语句的函数执行完毕时,延迟函数(defer
后的函数)才会被执行,而不管包含defer
语句的函数是通过return
的正常结束,还是由于panic
导致的异常结束。你可以在一个函数中执行多条defer
语句,它们的执行顺序与声明顺序相反。
当发生像数组下标越界或类型断言失败这样的运行错误时,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
的东西。这是个强大的工具,请明智谨慎地使用它。
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
延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。
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
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
是最后执行,而a
和b
已经被赋值成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
是一个内建的函数,可以让进入令人恐慌的流程中的goroutine
恢复过来。recover
仅在延迟函数中有效。在正常的执行过程中,调用recover
会返回nil
,并且没有其它任何效果。如果当前的goroutine
陷入panic
,调用recover
可以捕获到panic
的输入值,并且恢复正常的执行。
一般情况下,recover()
应该在一个使用defer
关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine
中明确调用恢复过程(使用recover
关键字),会导致该goroutine
所属的进程打印异常信息后直接退出。
从程序栈的角度来讲,panic
会导致栈被展开直到defer
修饰的recover()
被调用或者程序中止。
注意:上面说的goroutine
是什么意思?Go
语言里面的并发使用的是Goroutine
,Goroutine
可以看做一种轻量级的线程,或者叫用户级线程。与Java
的Thread
很像。
被defer
的函数在return
之后执行,这个时机点正好可以捕获函数抛出的panic
,因而defer
的另一个重要用途就是执行recover
。
recover
只有在defer
中使用才更有意义,如果在其他地方使用,由于程序已经调用结束而提前返回而无法有效捕捉错误。
一个展示panic
,defer
和recover
怎么结合使用的完整例子如下:
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()
方法,并结合panic
和recover
,集中展示下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类型
- 邮箱 :charon.chui@gmail.com
- Good Luck!