go語言 異常處理
異常處理
Go語言追求簡潔優雅,所以,Go語言不支援傳統的 try…catch…finally 這種異常,因為Go語言的設計者們認為,將異常與控制結構混在一起會很容易使得程式碼變得混亂。因為開發者很容易濫用異常,甚至一個小小的錯誤都丟擲一個異常。在Go語言中,使用多值返回來返回錯誤。不要用異常代替錯誤,更不要用來控制流程。在極個別的情況下,也就是說,遇到真正的異常的情況下(比如除數為0了)。才使用Go中引入的Exception處理:defer, panic, recover。
1 error
Go語言內建了一個簡單的錯誤介面作為一種錯誤處理機制,介面定義如下:
type error interface {
Error() string
}
它包含一個 Error() 方法,返回值為string
Go的error構造有兩種方式,分別是
第一種:errors.New()
err := errors.New("This is an error")
if err != nil {
fmt.Print(err)
}
第二種:fmt.Errorf()
err := fmt.Errorf("This is an error")
if err != nil {
fmt.Print(err)
}
除了直接使用Go自帶的方法,還可以自定義錯誤。下面以自然數函式作為例子:
type NotNature float64
func (err NotNature) Error() string {
return fmt.Sprintf("自然數為大於或等於0的數: %v", float64(err))
}
func Nature(x float64) (float64,error) {
if x<0 {
return 0,NotNature(x)
} else {
return x,nil
}
}
func main() {
fmt.Println(Nature(1))
fmt.Println(Nature(-1))
}
需要注意一下幾點:
1.如果函式需要處理異常,通常將error作為多值返回的最後一個值,返回的error值為nil則表示無異常,非nil則是有異常。
2.一般先用if語句處理error!=nil,正常邏輯放if後面。
Go語言的error代表的並不是真“異常”,只是通過返回error來表示錯誤資訊,換句話說,不是執行時錯誤範圍預定義的錯誤,某種不符合期望的行為並不會導致程式無法執行(自然數函式例子),都應使用error進行異常處理。當程式出現重大錯誤,如陣列越界,才會將其當成真正的異常,並用panic來處理。
2 panic
Go不使用try…catch方法來處理異常,而是使用panic和recover
先上程式碼舉一個簡單的例子
func main() {
fmt.Println("Hello,Go!")
panic(errors.New(" i am a error"))
fmt.Println("hello,again!")
}
輸出:
Hello,Go!
panic: i am a error
goroutine 1 [running]:
main.main()
~/error.go:12 +0xb5
exit status 2
可以看到,panic後面的程式不會被執行了。但是我們捕捉異常並不是為了停止程式(一般情況),而是為了讓程式能正常執行下去,這時候就到recover出場了。
package main
import "fmt"
func main(){
defer func(){
fmt.Println("我是defer裡面第一個列印函式")
if err:=recover();err!=nil{
fmt.Println(err)
}
fmt.Println("我是defer裡面第二個列印函式")
}()
f()
}
func f(){
fmt.Println("1")
panic("我是panic")
fmt.Println("2")
}
輸出:
1
我是defer裡面第一個列印函式
我是panic
我是defer裡面第二個列印函式
可以看到,f函式一開始正常列印,當遇到panic,就跳到defer函式,執行defer函式裡的內容
需要注意的是,defer函式裡列印的err其實就是panic裡面的內容。
下面詳細介紹一下panic和recover的原理。首先來看一下panic和recover的官方定義
func panic(v interface{})
內建函式panic會停止當前goroutine的正常執行。當函式F呼叫panic時,F的正常執行立即停止。任何被F延遲執行的函式都將以正常的方式執行,然後F返回其呼叫者。對呼叫方G來說,對F的呼叫就像呼叫panic一樣,終止G的執行並執行任何延遲的函式。直到執行goroutine中的所有函式都按逆序停止。此時,程式將以非0退出程式碼終止。此終止序列稱為panicking,可由內建函式recover控制。
func recover() interface{}
recover內建函式允許程式管理panicking的goroutine的行為。在defer函式(但不是它呼叫的任何函式)內執行恢復呼叫,通過恢復正常執行來停止panicking序列,並檢索傳遞給panic呼叫的錯誤值。如果在defer函式之外呼叫recover,則不會停止panicking的序列。在這種情況下,或者當goroutine不panicking時,或者提供給panic的引數是nil,recover返回nil。因此,recover的返回值報告goroutine是否panicking
ps:defer和recover必須在panic之前定義,否則無效。
3 原始碼分析
errors.New的定義如下
// src/errors/errors.go
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
1.New函式返回格式為給定文字的錯誤
2.即使文字是相同的,每次對New的呼叫都會返回一個不同的錯誤值。
使用
1 defer
defer是go語言中的關鍵字,延遲指定函式的執行。通常在資源釋放、連線關閉、函式結束時呼叫。多個defer為堆疊結構,先進後出,也就是先進的後執行。defer可用於異常丟擲後的處理。
2 panic
panic是go語言中的內建函式,丟擲異常(類似java中的throw)。其函式定義為:
func panic(v interface{})
3 recover
recover() 是go語言中的內建函式,獲取異常(類似java中的catch),多次呼叫時,只有第一次能獲取值。其函式定義為:
func recover() interface{}