Panic,堆棧跟蹤以及如何恢復【最佳實踐】(譯文)

做者:Stefan Nilssonhtml

原文網址:yourbasic.org/golang/reco…git

Panic 是 Go 中的一個異常

Panics 相似於 C++ 和 Java 異常,但僅適用於運行時錯誤,例如跟隨一個 nil 指針或試圖對數組訪問超出範圍的索引。爲了表示諸如文件結束之類的事件,Go 程序使用內置 error 類型。有關錯誤的更多信息,請參見 錯誤處理最佳實踐3種建立錯誤的簡單方法github

Panic 中止 goroutine 的正常執行golang

  • 程序出現 panic 時,它將當即開始展開調用堆棧。
  • 一直持續到程序崩潰並打印堆棧跟蹤,
  • 或直到調用內置的恢復功能。

panic 是由運行時錯誤或對內置函數 panic 的顯式調用引發的。編程

堆棧跟蹤記錄

堆棧跟蹤記錄 —— 全部活動堆棧幀的報告 —— 一般在 panic 發生時將其打印到控制檯。堆棧跟蹤對於調試很是有用:數組

  • 您不只能夠看到錯誤發生的地方,
  • 並且能夠看到程序是如何到達這個地方的。

解釋堆棧跟蹤

這是一個堆棧跟蹤的示例:bash

goroutine 11 [running]:
testing.tRunner.func1(0xc420092690)
    /usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x53f820, 0x594da0)
    /usr/local/go/src/runtime/panic.go:491 +0x283
github.com/yourbasic/bit.(*Set).Max(0xc42000a940, 0x0)
    ../src/github.com/bit/set_math_bits.go:137 +0x89
github.com/yourbasic/bit.TestMax(0xc420092690)
    ../src/github.com/bit/set_test.go:165 +0x337
testing.tRunner(0xc420092690, 0x57f5e8)
    /usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
    /usr/local/go/src/testing/testing.go:789 +0x2de
複製代碼

能夠從下至上閱讀:數據結構

  • testing.(*T).Run 調用了 testing.tRunner,
  • testing.tRunner 調用了 bit.TestMax,
  • bit.TestMax 調用了 bit.(*Set).Max,
  • bit.(*Set).Max 調用了 panic,
  • panic 調用了 testing.tRunner.func1

縮進的行顯示了調用該函數的源文件和行號。十六進制數字表示參數值,包括指針和內部數據結構的值。Go 中的堆棧跟蹤 具備更多詳細信息。框架

打印並記錄堆棧跟蹤

要打印當前 goroutine 的堆棧跟蹤,請使用包 runtime/debug 中的debug.PrintStack函數

您還能夠經過調用 runtime.Stack 以編程方式檢查當前的堆棧跟蹤

詳細程度

變量 GOTRACEBACK 控制 Go 程序失敗時生成的輸出量。

  • GOTRACEBACK = none 徹底忽略 goroutine 堆棧跟蹤。
  • GOTRACEBACK = single(默認)爲當前goroutine打印堆棧跟蹤, 從而消除運行時系統內部的功能。若是沒有當前goroutine或故障是運行時內部的,則故障會打印全部goroutine的堆棧跟蹤。
  • GOTRACEBACK = all 爲全部用戶建立的goroutine添加堆棧跟蹤。
  • GOTRACEBACK = system 與其餘系統同樣,可是爲運行時函數添加了堆棧框架,並顯示了運行時在內部建立的 goroutine。

恢復和捕獲 Panic

內置的 recover 函數可用於從新得到對異常程序的控制並恢復正常執行。

  • 調用 recover 將中止展開並返回傳遞給 panic 的參數。
  • 若是 goroutine 沒有異常,則恢復將返回 nil。

由於展開時運行的惟一代碼是在 defer 函數內部,因此 recover 僅在此類函數內部有用。

Panic 處理程序示例

func main() {
	n := foo()
	fmt.Println("main received", n)
}

func foo() int {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	m := 1
	panic("foo: fail")
	m = 2
	return m
}
複製代碼
foo: fail
main received 0
複製代碼

因爲 panic 是在 foo 返回值以前發生的,所以 n 仍然具備其初始零值。

返回值

要在發生 panic 時返回值,必須使用命名返回值。

func main() {
	n := foo()
	fmt.Println("main received", n)
}

func foo() (m int) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			m = 2
		}
	}()
	m = 1
	panic("foo: fail")
	m = 3
	return m
}
複製代碼
foo: fail
main received 2
複製代碼

測試 Panic(實用功能)

在此示例中,咱們使用反射來檢查接口變量列表是否具備與給定函數的參數相對應的類型。若是是這樣,咱們使用這些參數調用該函數以檢查是否有 panic。

// Panics tells if function f panics with parameters p.
func Panics(f interface{}, p ...interface{}) bool {
	fv := reflect.ValueOf(f)
	ft := reflect.TypeOf(f)
	if ft.NumIn() != len(p) {
		panic("wrong argument count")
	}
	pv := make([]reflect.Value, len(p))
	for i, v := range p {
		if reflect.TypeOf(v) != ft.In(i) {
			panic("wrong argument type")
		}
		pv[i] = reflect.ValueOf(v)
	}
	return call(fv, pv)
}

func call(fv reflect.Value, pv []reflect.Value) (b bool) {
	defer func() {
		if err := recover(); err != nil {
			b = true
		}
	}()
	fv.Call(pv)
	return
}
複製代碼

掃描下方二維碼,關注Feed, 按期推送最新隨筆

公衆號 Feed 二維碼
相關文章
相關標籤/搜索