W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://gopl-zh.github.io/ch5/ch5-09.html
Go的類型系統(tǒng)會(huì)在編譯時(shí)捕獲很多錯(cuò)誤,但有些錯(cuò)誤只能在運(yùn)行時(shí)檢查,如數(shù)組訪問(wèn)越界、空指針引用等。這些運(yùn)行時(shí)錯(cuò)誤會(huì)引起panic異常。
一般而言,當(dāng)panic異常發(fā)生時(shí),程序會(huì)中斷運(yùn)行,并立即執(zhí)行在該goroutine(可以先理解成線程,在第8章會(huì)詳細(xì)介紹)中被延遲的函數(shù)(defer 機(jī)制)。隨后,程序崩潰并輸出日志信息。日志信息包括panic value和函數(shù)調(diào)用的堆棧跟蹤信息。panic value通常是某種錯(cuò)誤信息。對(duì)于每個(gè)goroutine,日志信息中都會(huì)有與之相對(duì)的,發(fā)生panic時(shí)的函數(shù)調(diào)用堆棧跟蹤信息。通常,我們不需要再次運(yùn)行程序去定位問(wèn)題,日志信息已經(jīng)提供了足夠的診斷依據(jù)。因此,在我們填寫(xiě)問(wèn)題報(bào)告時(shí),一般會(huì)將panic異常和日志信息一并記錄。
不是所有的panic異常都來(lái)自運(yùn)行時(shí),直接調(diào)用內(nèi)置的panic函數(shù)也會(huì)引發(fā)panic異常;panic函數(shù)接受任何值作為參數(shù)。當(dāng)某些不應(yīng)該發(fā)生的場(chǎng)景發(fā)生時(shí),我們就應(yīng)該調(diào)用panic。比如,當(dāng)程序到達(dá)了某條邏輯上不可能到達(dá)的路徑:
switch s := suit(drawCard()); s {
case "Spades": // ...
case "Hearts": // ...
case "Diamonds": // ...
case "Clubs": // ...
default:
panic(fmt.Sprintf("invalid suit %q", s)) // Joker?
}
斷言函數(shù)必須滿足的前置條件是明智的做法,但這很容易被濫用。除非你能提供更多的錯(cuò)誤信息,或者能更快速的發(fā)現(xiàn)錯(cuò)誤,否則不需要使用斷言,編譯器在運(yùn)行時(shí)會(huì)幫你檢查代碼。
func Reset(x *Buffer) {
if x == nil {
panic("x is nil") // unnecessary!
}
x.elements = nil
}
雖然Go的panic機(jī)制類似于其他語(yǔ)言的異常,但panic的適用場(chǎng)景有一些不同。由于panic會(huì)引起程序的崩潰,因此panic一般用于嚴(yán)重錯(cuò)誤,如程序內(nèi)部的邏輯不一致。勤奮的程序員認(rèn)為任何崩潰都表明代碼中存在漏洞,所以對(duì)于大部分漏洞,我們應(yīng)該使用Go提供的錯(cuò)誤機(jī)制,而不是panic,盡量避免程序的崩潰。在健壯的程序中,任何可以預(yù)料到的錯(cuò)誤,如不正確的輸入、錯(cuò)誤的配置或是失敗的I/O操作都應(yīng)該被優(yōu)雅的處理,最好的處理方式,就是使用Go的錯(cuò)誤機(jī)制。
考慮regexp.Compile函數(shù),該函數(shù)將正則表達(dá)式編譯成有效的可匹配格式。當(dāng)輸入的正則表達(dá)式不合法時(shí),該函數(shù)會(huì)返回一個(gè)錯(cuò)誤。當(dāng)調(diào)用者明確的知道正確的輸入不會(huì)引起函數(shù)錯(cuò)誤時(shí),要求調(diào)用者檢查這個(gè)錯(cuò)誤是不必要和累贅的。我們應(yīng)該假設(shè)函數(shù)的輸入一直合法,就如前面的斷言一樣:當(dāng)調(diào)用者輸入了不應(yīng)該出現(xiàn)的輸入時(shí),觸發(fā)panic異常。
在程序源碼中,大多數(shù)正則表達(dá)式是字符串字面值(string literals),因此regexp包提供了包裝函數(shù)regexp.MustCompile檢查輸入的合法性。
package regexp
func Compile(expr string) (*Regexp, error) { /* ... */ }
func MustCompile(expr string) *Regexp {
re, err := Compile(expr)
if err != nil {
panic(err)
}
return re
}
包裝函數(shù)使得調(diào)用者可以便捷的用一個(gè)編譯后的正則表達(dá)式為包級(jí)別的變量賦值:
var httpSchemeRE = regexp.MustCompile(`^https?:`) //"http:" or "https:"
顯然,MustCompile不能接收不合法的輸入。函數(shù)名中的Must前綴是一種針對(duì)此類函數(shù)的命名約定,比如template.Must(4.6節(jié))
func main() {
f(3)
}
func f(x int) {
fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
defer fmt.Printf("defer %d\n", x)
f(x - 1)
}
上例中的運(yùn)行輸出如下:
f(3)
f(2)
f(1)
defer 1
defer 2
defer 3
當(dāng)f(0)被調(diào)用時(shí),發(fā)生panic異常,之前被延遲執(zhí)行的3個(gè)fmt.Printf被調(diào)用。程序中斷執(zhí)行后,panic信息和堆棧信息會(huì)被輸出(下面是簡(jiǎn)化的輸出):
panic: runtime error: integer divide by zero
main.f(0)
src/gopl.io/ch5/defer1/defer.go:14
main.f(1)
src/gopl.io/ch5/defer1/defer.go:16
main.f(2)
src/gopl.io/ch5/defer1/defer.go:16
main.f(3)
src/gopl.io/ch5/defer1/defer.go:16
main.main()
src/gopl.io/ch5/defer1/defer.go:10
我們?cè)谙乱还?jié)將看到,如何使程序從panic異常中恢復(fù),阻止程序的崩潰。
為了方便診斷問(wèn)題,runtime包允許程序員輸出堆棧信息。在下面的例子中,我們通過(guò)在main函數(shù)中延遲調(diào)用printStack輸出堆棧信息。
gopl.io/ch5/defer2
func main() {
defer printStack()
f(3)
}
func printStack() {
var buf [4096]byte
n := runtime.Stack(buf[:], false)
os.Stdout.Write(buf[:n])
}
printStack的簡(jiǎn)化輸出如下(下面只是printStack的輸出,不包括panic的日志信息):
goroutine 1 [running]:
main.printStack()
src/gopl.io/ch5/defer2/defer.go:20
main.f(0)
src/gopl.io/ch5/defer2/defer.go:27
main.f(1)
src/gopl.io/ch5/defer2/defer.go:29
main.f(2)
src/gopl.io/ch5/defer2/defer.go:29
main.f(3)
src/gopl.io/ch5/defer2/defer.go:29
main.main()
src/gopl.io/ch5/defer2/defer.go:15
將panic機(jī)制類比其他語(yǔ)言異常機(jī)制的讀者可能會(huì)驚訝,runtime.Stack為何能輸出已經(jīng)被釋放函數(shù)的信息?在Go的panic機(jī)制中,延遲函數(shù)的調(diào)用在釋放堆棧信息之前。
![]() | ![]() |
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: