Golang panic 用法
Golang panic 用法
Go 語言追求簡潔優雅,所以,Go 語言不支持傳統的 try…catch…finally 這種異常,因爲 Go 語言的設計者們認爲,將異常與控制結構混在一起會很容易使得代碼變得混亂。因爲開發者很容易濫用異常,甚至一個小小的錯誤都拋出一個異常。在 Go 語言中,使用多值返回來返回錯誤。不要用異常代替錯誤,更不要用來控制流程。在極個別的情況下,也就是說,遇到真正的異常的情況下(比如除數爲 0 了)。才使用 Go 中引入的 Exception 處理:defer, panic, recover。
這幾個異常的使用場景可以這麼簡單描述:Go 中可以拋出一個 panic 的異常,然後在 defer 中通過 recover 捕獲這個異常,然後正常處理。
package main
import "fmt"
func main(){
fmt.Println("c")
defer func(){ // 必須要先聲明defer,否則不能捕獲到panic異常
fmt.Println("d")
if err:=recover();err!=nil{
fmt.Println(err) // 這裏的err其實就是panic傳入的內容,55
}
fmt.Println("e")
}()
f() //開始調用f
fmt.Println("f") //這裏開始下面代碼不會再執行
}
func f(){
fmt.Println("a")
panic("異常信息")
fmt.Println("b") //這裏開始下面代碼不會再執行
fmt.Println("f")
}
輸出結果:
c
a
d
異常信息
e
-
內建函數
-
假如函數 F 中書寫了 panic 語句,會終止其後要執行的代碼,在 panic 所在函數 F 內如果存在要執行的 defer 函數列表,按照 defer 的逆序執行
比如panic函數內有:
defer 函數1
defer 函數2
defer 函數3
那麼執行順序就是:
函數3
函數2
函數1
-
返回函數 F 的調用者 G,在 G 中,調用函數 F 語句之後的代碼不會執行,假如函數 G 中存在要執行的 defer 函數列表,按照 defer 的逆序執行,這裏的 defer 有點類似 try-catch-finally 中的 finally
-
到 goroutine 整個退出,並報告錯誤
recover:
-
內建函數
-
用來控制一個 goroutine 的 panicking 行爲,捕獲 panic,從而影響應用的行爲
-
一般的調用建議 (如上面的例子)
a). 在 defer 函數中,通過 recever 來終止一個 gojroutine 的 panicking 過程,從而恢復正常代碼的執行
b). 可以獲取通過 panic 傳遞的 error
簡單來講:go 中可以拋出一個 panic 的異常,然後在 defer 中通過 recover 捕獲這個異常,然後正常處理。
注意:利用 recover 處理 panic 指令,defer 必須在 panic 之前聲明,否則當 panic 時,recover 無法捕獲到 panic.
應用示例:
比如 go 編譯器的源碼
註釋: 編譯器是構建正在運行的二進制文件的編譯器工具鏈的名稱。已知的工具鏈爲:
gc Also known as cmd/compile.
gccgo The gccgo front end, part of the GCC compiler suite.
介紹 錯誤和異常 (errors and exceptions)
大多數編程語言支持 exception 作爲處理 error 的標準方式,比如 Java,Python。雖然方便,但也會帶來許多問題,這就是爲什麼他們不喜歡其他語言或者風格。對 exception 的主要吐槽點是它們爲控制流引入了 "side channel",當閱讀代碼的時候,你必須時刻記住這個 exception 引發的流程控制方向。這也導致某些代碼閱讀起來比較困難 [1]。
讓我們開始具體談談 Go 中的錯誤處理。我假定你知道 Go 中錯誤處理的 “標準” 方式。下面如何打開文件的代碼:
f, err := os.Open("file.txt")
if err != nil {
// handle error here
}
// do stuff with f here
如果文件不存在, os.Open() 函數將返回一個非空 error ,在其他語言中這樣的錯誤處理是完全不同的,比如 Python 中的內建 open() 函數將在錯誤發生時拋出異常。
try:
with open("file.txt") as f:
# do stuff with f here
except OSError as err:
# handle exception err here
Python 始終堅持通過 exception 來處理 error。因爲這種無處不在的錯誤處理方式導致經常被吐槽。甚至利用 exception 作爲序列結束的信號。到底 exception 的真正含義是什麼?以下是來自 Rob Pike 在郵件中 對此的貼切闡述,其中塑造了現有的 Go panic/recover 機制雛形。
這正是提案試圖避免的那種事情。Panic 和 recover 不是通常意義的異常機制。通常的方式是將 exception 和一個控制結構相關聯,鼓勵細粒度的 exception 處理,導致代碼往往不易閱讀。在 error 和調用一個 panic 之間確實存在差異,而且我們希望這個差異很重要。在 Java 中打開一個文件會拋出異常。在我的經驗中,打開文件失敗是最平常不過的事。而且還需要我寫許多代碼來處理這樣的 exception。
客觀的講。exception 的支持者嘲笑 Go 的這種過於明確的 error 處理有多方面的原因。首先,請注意上面兩個例子中代碼的順序。在 Python 中,程序的主要流程緊跟在 open 調用之後,並且錯誤處理被委託給後一階段(更不用說在許多情況下,異常將被堆棧中更上一級的函數捕獲到而不是在此函數中)。另一方面,在 Go 中,立刻處理錯誤這種方式,可能會使主程序流程混淆。此外,Go 的錯誤處理非常冗長 - 這是該語言的主要吐槽點之一。我將在後面提到一種可能的方法來解決這個問題。
除了上面 Rob 的引用之外,在 FAQ 中總結了 Go 的 exception 哲學。
我們認爲將異常耦合到控制結構(如 try-catch-finally 慣用語)會導致代碼錯綜複雜。它還傾向於鼓勵程序員標記太多普通錯誤,例如打開文件失敗。
然而,在某些情況下,具有類似異常的機制實際上是有用的 ; 像 Go 這樣的高級語言甚至是必不可少的。這就是存在 panic 和 recover 的原因。
偶爾的 panic 是必要的
Go 是一種安全的語言,運行時檢查一些嚴重的編程錯誤。例如在你訪問超出 slice 邊界的元素時,這種行爲是未定義的,因此 Go 會在運行時 panic。例如下面的小程序。
package main
import (
"fmt"
)
func main() {
s := make([]string, 3)
fmt.Println(s[5])
}
程序將終止於一個運行時 error。
panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
/tmp/sandbox209906601/main.go:9 +0x40
其他一些會引發 panic 的就是通過值爲 nil 的指針訪問結構體的字段,關閉已經關閉的 channel 等。怎樣選擇性的 panic ?可以通過訪問 slice 時返回 result,error 兩個值的方式實現。也可以將 slice 的元素賦值給一個可能返回 error 的函數,但是這樣會將代碼變複雜。想象一下,寫一個小片段,foo,bar,baz 都只是一個字符串的一個 slice,實現片段之間的拼接。
foo[i] = bar[i] + baz[i]
就會變成下面這樣冗長的代碼:
br, err := bar[i]
if err != nil {
return err
}
bz, err := baz[i]
if err != nil {
return err
}
err := assign_slice_element(foo, i, br + bz)
if err != nil {
return err
}
這不是開玩笑,不同語言處理這樣的方式是不一樣的。如果 slices/lists/arrays 的指針 i 越界了,在 Python 和 Java 中就會拋出異常。C 中沒有越界檢查,所以你就可以盡情的蹂躪邊界外的內存空間,最後將導致程序崩潰或者暴露安全漏洞。C++ 中將採用折中的處理方式。性能優先的模塊採用這種不安全的 C 模式,其他模塊(比如 std::vector::at)採用拋出異常的方式。
因爲上面重寫的小片段變得如此冗長是不可接受的。Go 選擇了 panic ,這是一種類似異常的機制,在代碼中保留了像 bugs 這樣最原始的異常條件。
這不只是內建代碼能夠這樣用,自定義代碼也可以在任何需要的地方調用 panic。在有些可能導致可怕錯誤的地方還鼓勵使用 panic 拋出 error,比如 bug 或者一些關鍵因素被違反的時候。比如在 swich 的某個 case 在當前上下文中是不可能發生的,在這種 case 中只有一個 panic 函數。這無形中等價於 Python 中的 raise 或者 C++ 中的 throw。這也強有力的證明了在捕獲異常方面 Go 的異常處理的特殊之處。
轉自:
zhuanlan.zhihu.com/p/373653492
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Rp2cgPsDOnM1gvgdR9louQ