在通常情況下,函數向其調用方報告錯誤的方式都是返回一個error類型的值。但是,
當遇到致命錯誤的時候,很可能會使程序無法繼續運行。這時,上述錯誤處理方式就太
不適合了,Go推薦通過調用panic函數來報告致命錯誤。
1.panic
為了報告運行期間的致命錯誤,Go內建了專用函數panic,該函數用於停止當前的
控制流程並引發一個運行時恐慌。它可以接受一個任意類型的參數值,不過這個參數值
的類型常常會是string或者error,因為這樣更容易描述運行時恐慌的詳細信息。請看下
面的例子:
func main(){
outerFunc()
}
func outerFunc(){
innerFunc()
}
func innerFunc(){
panic(errors.New("An intended fatal errorl"))
}
當調用innerFunc函數中的panic函數後,innerFunc的執行會被停止。緊接著,流程
控制權會交回給調用方 outerFunc函數。然後,outerFunc函數的執行也將被停止。運行時
恐慌就這樣沿著調用棧反方向進行傳播,直至到達當前goroutine的調用棧的最頂層。一
旦達到頂層,就意味著該goroutine調用棧中所有函數的執行都已經被停止了,程序已經
崩潰。
當然,運行時恐慌並不都是通過調用panic函數的方式引發的,也可以由Go的運行
時系統來引發。例如:
myIndex:=4
ia:=[3]int{1,2,3}
_=ia[myIndex]
這個示例中的第3行代碼會引發一個運行時恐慌,因為它造成了一個數組訪問越界
的運行時錯誤。這個運行時恐慌就是由Go的運行時系統報告的。它相當於我們顯式地調
用panic函數並傳入一個runtime.Error類型的參數值。順便說一句,runtime.Error是一個
接口類型,並且內嵌了Go內置的error接口類型。
顯然,我們都不希望程序崩潰。那麼,怎樣“攔截”一個運行時恐慌呢?
2.recover
運行時恐慌一旦被引發,就會向調用方傳播直至程序崩潰。Go提供了專用於“攔截”
運行時恐慌的內建函數recover,它可以使當前的程序從恐慌狀態中恢復並重新獲得流程
控制權。recover 函數被調用後,會返回一個interface{}類型的結果。如果當時的程序正
處於運行時恐慌的狀態,那麼這個結果就會是非nil的。
recover 函數應該與defer語句配合起來使用,例如:
defer func(){
ifp:=recover();pl=nil{
fmt.Printf(“Recovered panic:%s\\n",p)
}
}()
把此類代碼放在函數體的開始處,這樣可以有效防止該函數及其下層調用中的代碼
引發運行時恐慌。一旦發現recover函數的調用結果非ni1,就應該採取相應的措施。
值得一提的是,Go標準庫中有一種常見的用法值得我們參考。請看標準庫代碼包fmt
func(s *ss)Token(skipSpace bool,f func(rune)bool)(tok []byte,err error){
defer func(){
ife:=recover();el=nil{
if se,ok:=e.(scanError);ok{
err=se.err
}else{
panic(e)
}
}
}()
//省略部分代碼
}
在Token函數包含的延遲函數中,當運行時恐慌攜帶值的類型是fmt.scanError時,
這個值就會被賦值給代表結果值的變量err,否則運行時恐慌就會被重新引發。如果這個
重新引發的運行時恐慌傳遞到了調用棧的最頂層,那麼標準輸出上就會打印出類似這樣
的內容:
panic:
panic:
goroutine 1[running]:
main.func·001()
goroutine 2[runnable]:
exit status 2
這裡展現的慣用法有兩個,如下。
可以把運行時恐慌的攜帶值轉換為error類型值,並當作常規結果返回給調用方。
這樣既阻止了恐慌的擴散,又傳遞了引起恐慌的原因。
檢查運行時恐慌攜帶值的類型,並根據類型做不同的後續動作,這樣可以精確地
控制程序的錯誤處理行為。
閱讀更多 80後老程序員 的文章