一 error接口 Go语言引入了一个关于错误处理的标准模式,即error
接口,它是Go语言内建的接口类型,该接口的定义如下:
1 2 3 type error interface { Error() string }
Go语言的标准库代码包errors
为用户提供如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 package errors type errorString struct { text string } func New(text string) error { return &errorString{text} } func (e *errorString) Error() string { return e.text }
我们可以在编码中通过实现 error
接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New
可返回一个错误信息:
1 2 3 4 5 6 func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 实现 }
另一个可以生成error
类型值的方法是调用fmt
包中的Errorf
函数:
1 2 3 4 5 6 package fmt import "errors" func Errorf(format string, args ...interface{}) error { return errors.New(Sprintf(format, args...)) }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 import ( "errors" "fmt" ) func main() { var err1 error = errors.New("a normal err1") fmt.Println(err1) //a normal err1 var err2 error = fmt.Errorf("%s", "a normal err2") fmt.Println(err2) //a normal err2 }
函数通常在最后的返回值中返回错误信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import ( "errors" "fmt" ) func Divide(a, b float64) (result float64, err error) { if b == 0 { result = 0.0 err = errors.New("runtime error: divide by zero") return } result = a / b err = nil return } func main() { r, err := Divide(10.0, 0) if err != nil { fmt.Println(err) //错误处理 runtime error: divide by zero } else { fmt.Println(r) // 使用返回值 } }
二 panic 在通常情况下,向程序使用方报告错误状态的方式可以是返回一个额外的error
类型值。
但是,当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起painc
异常。这时,上述错误处理方式显然就不适合了。反过来讲,在一般情况下,我们不应通过调用panic
函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用panic
。
一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine
(可以先理解成线程,在中被延迟的函数(defer
机制)。随后,程序崩溃并输出日志信息。日志信息包括panic value
和函数调用的堆栈跟踪信息。
不是所有的panic
异常都来自运行时,直接调用内置的panic
函数也会引发panic
异常;panic
函数接受任何值作为参数。
1 func panic(v interface{})
调用panic函数引发的panic异常:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "fmt" ) func TestA() { fmt.Println("func TestA()") } func TestB() { panic("func TestB(): panic") } func TestC() { fmt.Println("func TestC()") } func main() { TestA() TestB() //TestB()发生异常,中断程序 TestC() }
运行结果:
1 2 3 4 5 6 7 8 9 10 C:/Users/qingteng/Desktop/aa/src/src.exe [C:/Users/qingteng/Desktop/aa/src] func TestA() panic: func TestB(): panic goroutine 1 [running]: main.TestB(...) C:/Users/qingteng/Desktop/aa/src/main.go:12 main.main() C:/Users/qingteng/Desktop/aa/src/main.go:21 +0xa5 错误: 进程退出代码 2.
内置的panic函数引发的panic异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package main import ( "fmt" ) func TestA() { fmt.Println("func TestA()") } func TestB(x int) { var a [10]int a[x] = 222 //x值为11时,数组越界 } func TestC() { fmt.Println("func TestC()") } func main() { TestA() TestB(11) //TestB()发生异常,中断程序 TestC() }
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 C:/Go/bin/go.exe build [C:/Users/qingteng/Desktop/aa/src] 成功: 进程退出代码 0. C:/Users/qingteng/Desktop/aa/src/src.exe [C:/Users/qingteng/Desktop/aa/src] func TestA() panic: runtime error: index out of range [11] with length 10 goroutine 1 [running]: main.TestB(...) C:/Users/qingteng/Desktop/aa/src/main.go:13 main.main() C:/Users/qingteng/Desktop/aa/src/main.go:22 +0x8c 错误: 进程退出代码 2.
三 recover 运行时panic
异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的,因为谁也不能保证程序不会发生任何运行时错误。
不过,Go语言为我们提供了专用于“拦截”运行时panic
的内建函数——recover
。它可以是当前的程序从运行时panic
的状态中恢复并重新获得流程控制权。
1 func recover() interface{}
注意:recover
只有在defer
调用的函数中有效。
3.1 调用内置函数recover
如果调用了内置函数recover
,并且定义该defer
语句的函数发生了panic
异常,recover
会使程序从panic
中恢复,并返回panic value
。导致panic
异常的函数不会继续运行,但能正常返回。在未发生panic
时调用recover
,recover
会返回nil
。
例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package main import ( "fmt" ) func TestA() { fmt.Println("func TestA()") } func TestB() (err error) { defer func() { //在发生异常时,设置恢复 if x := recover(); x != nil { //panic value被附加到错误信息中; //并用err变量接收错误信息,返回给调用者。 err = fmt.Errorf("internal error: %v", x) } }() panic("func TestB(): panic") } func TestC() { fmt.Println("func TestC()") } func main() { TestA() err := TestB() fmt.Println(err) TestC() }
运行结果为
1 2 3 4 5 6 7 C:/Go/bin/go.exe build [C:/Users/qingteng/Desktop/aa/src] 成功: 进程退出代码 0. C:/Users/qingteng/Desktop/aa/src/src.exe [C:/Users/qingteng/Desktop/aa/src] func TestA() internal error: func TestB(): panic func TestC() 成功: 进程退出代码 0.
3.2 延迟调用中引发的错误 延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后⼀个错误可被捕获:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import ( "fmt" ) func test() { defer func() { fmt.Println(recover()) }() defer func() { panic("defer panic") }() panic("test panic") } func main() { test() }
运行结果为
1 2 3 C:/Users/qingteng/Desktop/aa/src/src.exe [C:/Users/qingteng/Desktop/aa/src] defer panic 成功: 进程退出代码 0.
四 panic 与 recover区别 这里应该介绍一下 panic
与 recover
,一个用于主动抛出错误,一个用于捕获panic
抛出的错误。
概念
panic
与 recover
是 Go 的两个内置函数,这两个内置函数用于处理 Go 运行时的错误,panic
用于主动抛出错误,recover
用来捕获 panic
抛出的错误。
引发panic
有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。
发生panic
后,程序会从调用panic
的函数位置或发生panic
的地方立即返回,逐层向上执行函数的defer
语句,然后逐层打印函数调用堆栈,直到被recover
捕获或运行到最外层函数。
panic
不但可以在函数正常流程中抛出,在defer
逻辑里也可以再次调用panic
或抛出panic
。defer
里面的panic
能够被后续执行的defer
捕获。
recover
用来捕获panic
,阻止panic
继续向上传递。recover()
和defer
一起使用,但是defer
只有在后面的函数体内直接被掉用才能捕获panic
来终止异常,否则返回nil
,异常继续向外传递。
例子1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //以下捕获失败 defer recover() defer fmt.Prinntln(recover) defer func(){ func(){ recover() //无效,嵌套两层 }() }() //以下捕获有效 defer func(){ recover() }() func except(){ recover() } func test(){ defer except() panic("runtime error") }
例子2
多个panic只会捕捉最后一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import "fmt" func main(){ defer func(){ if err := recover() ; err != nil { fmt.Println(err) } }() defer func(){ panic("three") }() defer func(){ panic("two") }() panic("one") }
使用场景
一般情况下有两种情况用到:
程序遇到无法执行下去的错误时,抛出错误,主动结束运行。
在调试程序时,通过 panic 来打印堆栈,方便定位错误。