一 error接口 Go语言引入了一个关于错误处理的标准模式,即error接口,它是Go语言内建的接口类型,该接口的定义如下:
type error interface { Error() string }
Go语言的标准库代码包errors为用户提供如下方法:
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可返回一个错误信息:
func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 实现 }
另一个可以生成error类型值的方法是调用fmt包中的Errorf函数:
package fmt import "errors" func Errorf(format string, args ...interface{}) error { return errors.New(Sprintf(format, args...)) }
示例代码:
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 }
函数通常在最后的返回值中返回错误信息:
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函数接受任何值作为参数。
func panic(v interface{})
调用panic函数引发的panic异常: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() }
运行结果:
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异常:
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() }
运行结果:
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的状态中恢复并重新获得流程控制权。
func recover() interface{}
注意:recover只有在defer调用的函数中有效。
3.1 调用内置函数recover 如果调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
例代码:
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() }
运行结果为
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 延迟调用中引发的错误 延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后⼀个错误可被捕获:
package main import ( "fmt" ) func test() { defer func() { fmt.Println(recover()) }() defer func() { panic("defer panic") }() panic("test panic") } func main() { test() }
运行结果为
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
//以下捕获失败 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只会捕捉最后一个:
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 来打印堆栈,方便定位错误。