9.1 error接口
Go語言引入了一個關(guān)于錯誤處理的標(biāo)準(zhǔn)模式,即error接口,它是Go語言內(nèi)建的接口類型,該接口的定義如下:
type error interface {
Error() string
}
Go語言的標(biāo)準(zhǔn)庫代碼包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類型值的方法是調(diào)用fmt包中的Errorf函數(shù):
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
}
函數(shù)通常在最后的返回值中返回錯誤信息:
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) // 使用返回值
}
}
9.2 panic
在通常情況下,向程序使用方報告錯誤狀態(tài)的方式可以是返回一個額外的error類型值。
但是,當(dāng)遇到不可恢復(fù)的錯誤狀態(tài)的時候,如數(shù)組訪問越界、空指針引用等,這些運行時錯誤會引起painc異常。這時,上述錯誤處理方式顯然就不適合了。反過來講,在一般情況下,我們不應(yīng)通過調(diào)用panic函數(shù)來報告普通的錯誤,而應(yīng)該只把它作為報告致命錯誤的一種方式。當(dāng)某些不應(yīng)該發(fā)生的場景發(fā)生時,我們就應(yīng)該調(diào)用panic。
一般而言,當(dāng)panic異常發(fā)生時,程序會中斷運行,并立即執(zhí)行在該goroutine(可以先理解成線程,在中被延遲的函數(shù)(defer 機制)。隨后,程序崩潰并輸出日志信息。日志信息包括panic value和函數(shù)調(diào)用的堆棧跟蹤信息。
不是所有的panic異常都來自運行時,直接調(diào)用內(nèi)置的panic函數(shù)也會引發(fā)panic異常;panic函數(shù)接受任何值作為參數(shù)。
func panic(v interface{})
調(diào)用panic函數(shù)引發(fā)的panic異常:
func TestA() {
fmt.Println("func TestA()")
}
func TestB() {
panic("func TestB(): panic")
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
TestB()//TestB()發(fā)生異常,中斷程序
TestC()
}
運行結(jié)果:

內(nèi)置的panic函數(shù)引發(fā)的panic異常:
func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
var a [10]int
a[x] = 222 //x值為11時,數(shù)組越界
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
TestB(11)//TestB()發(fā)生異常,中斷程序
TestC()
}
運行結(jié)果:

9.3 recover
運行時panic異常一旦被引發(fā)就會導(dǎo)致程序崩潰。這當(dāng)然不是我們愿意看到的,因為誰也不能保證程序不會發(fā)生任何運行時錯誤。
不過,Go語言為我們提供了專用于“攔截”運行時panic的內(nèi)建函數(shù)——recover。它可以是當(dāng)前的程序從運行時panic的狀態(tài)中恢復(fù)并重新獲得流程控制權(quán)。
func recover() interface{}
注意:recover只有在defer調(diào)用的函數(shù)中有效。
如果調(diào)用了內(nèi)置函數(shù)recover,并且定義該defer語句的函數(shù)發(fā)生了panic異常,recover會使程序從panic中恢復(fù),并返回panic value。導(dǎo)致panic異常的函數(shù)不會繼續(xù)運行,但能正常返回。在未發(fā)生panic時調(diào)用recover,recover會返回nil。
示例代碼:
func TestA() {
fmt.Println("func TestA()")
}
func TestB() (err error) {
defer func() { //在發(fā)生異常時,設(shè)置恢復(fù)
if x := recover(); x != nil {
//panic value被附加到錯誤信息中;
//并用err變量接收錯誤信息,返回給調(diào)用者。
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()
/*
運行結(jié)果:
func TestA()
internal error: func TestB(): panic
func TestC()
*/
}
延遲調(diào)用中引發(fā)的錯誤,可被后續(xù)延遲調(diào)用捕獲,但僅最后?個錯誤可被捕獲:
func test() {
defer func() {
fmt.Println(recover())
}()
defer func() {
panic("defer panic")
}()
panic("test panic")
}
func main() {
test()
//運行結(jié)果:defer panic
}