Go語言開發(七)、Go語言錯誤處理
一、defer延遲函數
1、defer延遲函數簡介
defer在聲明時不會立即執行,而是在函數return後,再按照FILO(先進後出)的原則依次執行每一個defer,一般用於異常處理、釋放資源、清理資料、記錄日誌等。
每次defer語句執行時,defer修飾的函數的傳回值和參數取值會照常進行計算和儲存,但是defer修飾的函數不會執行。等到上一級函數返回前,會按照defer的聲明順序倒序執行全部defer的函數。defer所修飾函數的任何傳回值都會被丟棄。
如果一個defer所修飾函數的值為nil,則defer的函數會在函數執行時panic(異常),而不會在defer語句執行時panic。defer所修飾函數的上一級函數即使拋出異常,defer所修飾函數也會被執行的,確保資源被合法釋放。
defer延遲函數使用樣本如下:
package mainimport "fmt"func deferTest(){ defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3)}func main() { deferTest()//3,2,1}
2、defer延遲函數應用
A、簡化資源回收
mu.Lock() defer mu.Unlock()
defer 有一定的開銷, 為了節省效能可以避免使用的defer
B、捕獲panic異常
Go語言中,panic用於拋出異常,,recover用於捕獲異常。
recover只能在defer語句中使用,直接調用recover是無效的。
package mainimport "fmt"func deferRecover(){ defer func () { if r := recover(); r != nil { fmt.Println("recover") } }() fmt.Println("exception will be happen") panic("exception has happped.") fmt.Println("return normally")}func main() { deferRecover()}
C、修改傳回值
defer可以用於在 return 後修改函數的傳回值。
package mainimport "fmt"func deferReturn(a,b int)(sum int){ defer func(){ sum += 100 }() sum = a + b return sum}func main() { sum := deferReturn(1,6) fmt.Println(sum)//107}
D、安全回收資源
func set(mu *sync.Mutex, arr []int, i, v int) { mu.Lock() defer mu.Unlock() arr[i] = v}
如果運行時拋出切片越界異常,可以保證mu.Unlock()被調用。
二、錯誤處理
1、錯誤處理簡介
Go語言通過內建的錯誤介面提供了簡單的錯誤處理機制。
error類型是一個介面類型,定義如下:
type error interface { Error() string}
Golang中引入error介面類型作為錯誤處理的標準模式,如果函數要返回錯誤,則傳回值類型列表中肯定包含error。
2、錯誤處理使用
package mainimport ( "fmt" "errors")//定義一個DivideError類型type DivideError struct { dividee int divider int}//實現error介面func (err *DivideError) Error() error{ strFormat := `Cannot proceed, the divider is zero.dividee: %d divider: 0` return errors.New(fmt.Sprintf(strFormat, err.dividee))}//定義除法運算func divide(vardividee int, vardivider int)(result int, errmsg error){ if vardivider == 0{ divideErr := DivideError{ dividee:vardividee, divider:vardivider, } errmsg = divideErr.Error() return 0,errmsg }else{ return vardividee/vardivider,nil }}func main() { //正常情況 if result, err := divide(100, 10); err != nil{ fmt.Println(err) }else{ fmt.Println("100/10 = ", result) } //當被除數為零的時候會返回錯誤資訊 if _, errorMsg := divide(100, 0); errorMsg != nil{ fmt.Println(errorMsg) }}
三、異常處理
1、異常處理簡介
Go使用panic()函數拋出異常,在defer語句中調用recover()函數捕獲異常。
func panic(interface{})//接受任意型別參數 無傳回值 func recover() interface{}//可以返回任意類型 無參數
panic()是一個內建函數,可以中斷原有的控制流程程,進入一個panic流程中。當函數F調用panic,函數F的執行被中斷,但F中的延遲函數(必須是在panic前的已載入的defer)會正常執行,然後F函數逐層向上返回,直到發生panic的goroutine中所有調用的函數返回,此時程式退出。異常可以直接調用panic產生,也可以由執行階段錯誤產生,例如訪問越界的數組。
recover()是一個內建函數,可以讓進入panic流程中的goroutine恢複過來。recover僅在延遲函數中有效。在正常的執行過程中,調用recover會返回nil,並且沒有其它任何效果。如果當前的goroutine陷入panic,調用recover可以捕獲到panic的輸入值,並且恢複正常的執行。
一般情況下,recover()應該在一個使用defer關鍵字的函數中執行以有效截取錯誤處理流程。如果沒有在發生異常的goroutine中明確調用恢複過程(使用recover關鍵字),會導致goroutine所屬的進程列印異常資訊後直接退出。
2、異常處理使用樣本
package mainimport ( "errors" "fmt")//定義一個DivideError類型type DivideError struct { dividee int divider int}//實現error介面func (err *DivideError) Error() error{ strFormat := `Cannot proceed, the divider is zero.dividee: %d divider: 0` return errors.New(fmt.Sprintf(strFormat, err.dividee))}//定義除法運算func divide(dividee int, divider int)(result int){ defer func() { if r := recover();r != nil{ divideErr := DivideError{ dividee:dividee, divider:divider, } fmt.Println(divideErr.Error()) } }() result = dividee/divider return result}func main() { a := divide(100,0) fmt.Println(a)}