在 go 語言中,有一個預約義的接口:error
,該接口自帶一個 Error()
方法,調用該方法會返回一個字符串。sql
type error interface {
Error() string
}
複製代碼
調用該方法,會返回當前錯誤的具體結果。通常有下面幾種方式生成 error。markdown
errors.New()
fmt.Errorf()
調用 errors.New()
會返回一個 error 類型的結構體,該結構體內部會實現一個 Error()
方法, 調用該方法返回的結果爲調用 errors.New()
方法時傳入的內容。數據結構
import (
"errors"
"fmt"
)
func divide(a, b int) (error, int) {
if b == 0 {
// 被除數爲0,則構造一個 error 結構體
return errors.New("被除數不能爲0"), 0
}
var result = a / b
return nil, result
}
func main() {
var err error // error 類型數據的初始值爲 nil,相似於 js 中的 null
var result int
err, result = divide(1, 0)
if err == nil {
// 若是 err 爲 nil,說明運行正常
fmt.Println("計算結果", result)
} else {
// 若是 err 不爲 nil,說明運行出錯
// 調用 error 結構體的 Error 方法,輸出錯誤緣由
fmt.Println("計算出錯", err.Error())
}
}
複製代碼
能夠看到,上面的代碼中,因爲調用 divide
除法方法時,因爲傳入的被除數爲 0。通過判斷,會拋出一個由 errors.New
構造的 error
類型的結構體。ide
咱們將調用 error.Error()
方法返回的結果輸出到控制檯,能夠發現其返回的結果,就是傳入 New
方法的值。函數
執行結果以下:ui
經過 fmt.Errorf()
方法構造的 error 結構體,與調用 errors.New()
方法的結果相似。不一樣的是,fmt.Errorf()
方法會進行一次數據的格式化。spa
func divide(a, b int) (error, int) {
if b == 0 {
// 將參數進行一次格式化,格式化後的字符串放入 error 中
return fmt.Errorf("數據 %d 不合法", b), 0
}
var result = a / b
return nil, result
}
err, result := divide(1, 0)
fmt.Println("計算出錯", err.Error())
複製代碼
執行結果以下:3d
panic()
至關於主動中止程序運行,調用時 panic()
時,須要傳入中斷緣由。調用後,會在控制檯輸出中斷緣由,以及中斷時的調用堆棧。咱們能夠改造一下以前的代碼:日誌
func divide(a, b int) (error, int) {
if b == 0 {
// 若是程序出錯,直接中止運行
panic("被除數不能爲0")
}
var result = a / b
return nil, result
}
func main() {
err, result := divide(1, 0)
fmt.Println("計算出錯", err.Error())
}
複製代碼
在運行到 panic()
處,程序直接中斷,並在控制檯打印出了中斷緣由。code
panic()
能夠理解爲,js 程序中的 throw new Error()
的操做。那麼,在 go 中有沒有辦法終止 panic()
,也就是相似於 try-catch
的操做,讓程序回到正常的運行邏輯中呢?
在介紹 recover()
方法以前,還須要介紹一個 go 語言中的另外一個關鍵字:defer
。
defer
後的語句會在函數進行 return 操做以前調用,經常使用於資源釋放
、錯誤捕獲
、日誌輸出
。
func getData(table, sql) {
defer 中斷鏈接()
db := 創建鏈接(table)
data := db.select(sql)
return data
}
複製代碼
defer
後的語句會被存儲在一個相似於棧的數據結構內,在函數結束的時候,被定義的語句按順序出棧,越後面定義的語句越先被調用。
func divide(a, b int) int {
defer fmt.Println("除數爲", b)
defer fmt.Println("被除數爲", a)
result := a / b
fmt.Println("計算結果爲", result)
return result
}
divide(10, 2)
複製代碼
上面的代碼中,咱們在函數開始運行的時候,先經過 defer
定義了兩個輸出語句,先輸出除數
,後輸出被除數
。
實際的運行結果是:
這和前面提到的,經過 defer
定義的語句會在函數結束的時候,按照出棧的方式進行執行,先定義的後執行。defer
除了會在函數結束的時候執行,出現異常的的時候也會先走 defer
的邏輯,也就是說,咱們在調用了 panic()
方法後,程序中斷過程當中,也會先將 defer
內的語句運行一遍。
這裏咱們從新定義以前的 divide
函數,在執行以前加上一個 defer
語句,defer
後面爲一個自執行函數,該函數內會調用 recover()
方法。
recover()
方法調用後,會捕獲到當前的 panic()
拋出的異常,並進行返回,若是沒有異常,則返回 nil
。
func divide(a, b int) int {
// 中斷以前,調用 defer 後定義的語句
defer func() {
if err := recover(); err != nil {
fmt.Println("捕獲錯誤", err)
}
}()
if b == 0 {
// 函數運行被中斷
panic("被除數不能爲0")
return 0
}
return a / b
}
result := divide(1, 0)
fmt.Println("計算結果", result)
複製代碼
上面的代碼運行後,咱們發現以前調用 panic()
中斷的程序被恢復了,並且後面的計算結果也正常進行輸出了。
這就有點相似於 try-catch
的邏輯了,只是 recover
須要放在 defer
關鍵詞後的語句中,更像是 catch
和 finally
的結合。