每當(dāng)函數(shù)返回時,我們應(yīng)該檢查是否有錯誤發(fā)生:但是這會導(dǎo)致重復(fù)乏味的代碼。結(jié)合 defer/panic/recover 機(jī)制和閉包可以得到一個我們馬上要討論的更加優(yōu)雅的模式。不過這個模式只有當(dāng)所有的函數(shù)都是同一種簽名時可用,這樣就有相當(dāng)大的限制。一個很好的使用它的例子是 web 應(yīng)用,所有的處理函數(shù)都是下面這樣:
func handler1(w http.ResponseWriter, r *http.Request) { ... }
假設(shè)所有的函數(shù)都有這樣的簽名:
func f(a type1, b type2)
參數(shù)的數(shù)量和類型是不相關(guān)的。
我們給這個類型一個名字:
fType1 = func f(a type1, b type2)
在我們的模式中使用了兩個幫助函數(shù):
1)check:這是用來檢查是否有錯誤和 panic 發(fā)生的函數(shù):
func check(err error) { if err != nil { panic(err) } }
2)errorhandler:這是一個包裝函數(shù)。接收一個 fType1 類型的函數(shù) fn 并返回一個調(diào)用 fn 的函數(shù)。里面就包含有 defer/recover 機(jī)制,這在 13.3 節(jié)中有相應(yīng)描述。
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if err, ok := recover().(error); ok {
log.Printf("run time panic: %v", err)
}
}()
fn(a, b)
}
}
當(dāng)錯誤發(fā)生時會 recover 并打印在日志中;除了簡單的打印,應(yīng)用也可以用 template 包(參見 15.7 節(jié))為用戶生成自定義的輸出。check() 函數(shù)會在所有的被調(diào)函數(shù)中調(diào)用,像這樣:
func f1(a type1, b type2) {
...
f, _, err := // call function/method
check(err)
t, err := // call function/method
check(err)
_, err2 := // call function/method
check(err2)
...
}
通過這種機(jī)制,所有的錯誤都會被 recover,并且調(diào)用函數(shù)后的錯誤檢查代碼也被簡化為調(diào)用 check(err) 即可。在這種模式下,不同的錯誤處理必須對應(yīng)不同的函數(shù)類型;它們(錯誤處理)可能被隱藏在錯誤處理包內(nèi)部。可選的更加通用的方式是用一個空接口類型的切片作為參數(shù)和返回值。
我們會在 15.5 節(jié)的 web 應(yīng)用中使用這種模式。
練習(xí)
練習(xí) 13.1:recover_dividebyzero.go
用示例 13.3 中的編碼模式通過整數(shù)除以 0 觸發(fā)一個運(yùn)行時 panic。
練習(xí) 13.2:panic_defer.go
閱讀下面的完整程序。不要執(zhí)行它,寫出程序的輸出結(jié)果。然后編譯執(zhí)行并驗(yàn)證你的預(yù)想。
// panic_defer.go
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
輸出:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
練習(xí) 13.3:panic_defer_convint.go
寫一個 ConvertInt64ToInt 函數(shù)把 int64 值轉(zhuǎn)換為 int 值,如果發(fā)生錯誤(提示:參見 4.5.2.1 節(jié))就 panic。然后在函數(shù) IntFromInt64 中調(diào)用這個函數(shù)并 recover,返回一個整數(shù)和一個錯誤。請測試這個函數(shù)!