在實際的項目中,對於異常的最佳實踐不少,在使用不一樣的語言開發不一樣類型的程序時有不一樣的建議。Go語言中沒有使用try...catch相似的異常處理機制,而是提供了panic和recover函數來處理所謂的運行時異常,也就是所謂的錯誤處理機制。配合defer語句和error接口開發者能夠很是靈活地處理運行時的錯誤和異常。bash
代碼中隨便使用panic顯然不是一個好方式,咱們知道在風險控制中,有所謂已知的未知和未知的未知,在Go程序設計中,對於前者咱們能夠經過預先開發的代碼分支來處理,對於後者就比較棘手了:ide
咱們應該認識到,panic函數是咱們和計算機都不但願看到的,應該在設計開發的時候充分考慮使用場景可能出現的狀況,處理好已知的未知,在肯定須要的地方使用panic機制。函數
defer關鍵字用來標記最後執行的Go語句,通常用在資源釋放、關閉鏈接等操做,會在函數關閉前調用。多個defer的定義與執行相似於棧的操做:先進後出,最早定義的最後執行。ui
package main
import (
"fmt"
)
func testA() int {
x := 5
defer func() {
x++
fmt.Printf("testA defer func-1[x=>%d]\n", x)
}()
defer func() {
x++
fmt.Printf("testA defer funn-2[x=>%d]\n", x)
}()
return x
}
func testB() (x int) {
x = 5
defer func() {
fmt.Println("testB defer func")
}()
x++
return
}
func main() {
a := testA()
b := testB()
fmt.Printf("testA return %d\n", a)
fmt.Printf("testB return %d\n", b)
}
複製代碼
執行後輸出結果:編碼
testA defer funn-2[x=>6]
testA defer func-1[x=>7]
testB defer func
testA return 5
testB return 6
複製代碼
分析如上代碼,return xxx
語句並非一條原子指令,在其執行的時候語句會分解成返回變量=xxx
和return
,最後執行return
。以前說defer
語句是在函數關閉的時候調用,確切的說是在執行return
語句的時候調用,注意是return
不是return xxx
。在同一個函數裏的defer會按照棧的先進後出原則執行。spa
Go語言經過支持多返回值,讓在運行時返回詳細的錯誤信息給調用者變得很是方便。咱們能夠在編碼中經過實現error接口類型來生成錯誤信息,error接口的定義以下:設計
type error interface {
Error() string
}
複製代碼
經過下面的例子來看一下error接口的使用:code
package main
import (
"fmt"
)
type error interface {
Error() string
}
// DivisionError 類型定義
type DivisionError struct {
dividend int
divider int
}
// Error 方法實現
func (de *DivisionError) Error() string {
strFormat := ` Cannot proceed, the divider is zero. dividend: %d divider: 0`
return fmt.Sprintf(strFormat, de.dividend)
}
// Divide 函數定義
func Divide(varDividend int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivisionError{
dividend: varDividend,
divider: varDivider,
}
errorMsg = dData.Error()
} else {
result = varDividend / varDivider
}
return
}
func main() {
result, err := Divide(100, 10)
if err == "" {
fmt.Printf("100/10=%d\n", result)
}
_, err = Divide(100, 0)
if err != "" {
fmt.Printf("errorMsg is: %s\n", err)
}
}
複製代碼
運行後能夠看到以下輸出:orm
100/10=10
errorMsg is:
Cannot proceed, the divider is zero.
dividend: 100
divider: 0
複製代碼
panic和recover是兩個內置函數,panic函數用於拋出異常,recover函數用於捕獲panic函數的參數信息。recover函數只有在defer語句調用的函數中直接調用時才能生效,若是goroutine沒有panic,那recover函數會返回nil。對象
package main
import (
"fmt"
)
// SimplePanicRecover panic/recover簡單的例子
func SimplePanicRecover() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Panic info is:", err)
}
}()
panic("SimplePanicRecover panic!")
}
// MultiPanicRecover 多個panic/recover
// 當defer中也調用了panic函數時,最後被調用的panic函數的參數會被後面的recover函數獲取到
// 一個函數中能夠定義多個defer函數,按照FILO的規則執行
func MultiPanicRecover() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Panic info is:", err)
}
}()
defer func() {
panic("MultiPanicRecover defer inner panic!")
}()
defer func() {
if err := recover(); err != nil {
fmt.Println("Panic info is:", err)
}
}()
panic("MultiPanicRecover panic!")
}
// NoPanicButHasRecover 只有panic沒有recover
// 若是函數沒有panic函數,調用recover函數不會獲取到任何信息,也不會影響當前進程
func NoPanicButHasRecover() {
if err := recover(); err != nil {
fmt.Println("NoPanicButHasRecover Panic info is:", err)
} else {
fmt.Println("NoPanicButHasRecover Panic info is:", err)
}
}
func main() {
SimplePanicRecover()
MultiPanicRecover()
NoPanicButHasRecover()
}
複製代碼
運行後能夠看到以下輸出:
Panic info is: SimplePanicRecover panic!
Panic info is: MultiPanicRecover panic!
Panic info is: MultiPanicRecover defer inner panic!
NoPanicButHasRecover Panic info is: <nil>
複製代碼