Golang 錯誤處理|Go主題月

構造 error

在 go 語言中,有一個預約義的接口:error,該接口自帶一個 Error() 方法,調用該方法會返回一個字符串。sql

type error interface {
  Error() string
}
複製代碼

調用該方法,會返回當前錯誤的具體結果。通常有下面幾種方式生成 error。markdown

  • errors.New()
  • fmt.Errorf()

errors.New()

調用 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()

經過 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() 與 recover()

panic()

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()

在介紹 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 關鍵詞後的語句中,更像是 catchfinally 的結合。

相關文章
相關標籤/搜索