13.5 一種用閉包處理錯誤的模式

每當函數返回時,我們應該檢查是否有錯誤發生:但是這會導致重複乏味的代碼。結合 defer/panic/recover 機制和閉包可以得到一個我們馬上要討論的更加優雅的模式。不過這個模式只有當所有的函數都是同一種簽名時可用,這樣就有相當大的限制。一個很好的使用它的例子是 web 應用,所有的處理函數都是下面這樣:

func handler1(w http.ResponseWriter, r *http.Request) { ... }

假設所有的函數都有這樣的簽名:

func f(a type1, b type2)

參數的數量和類型是不相關的。

我們給這個類型一個名字:

fType1 = func f(a type1, b type2)

在我們的模式中使用了兩個幫助函數:

1)check:這是用來檢查是否有錯誤和 panic 發生的函數:

func check(err error) { if err != nil { panic(err) } }

2)errorhandler:這是一個包裝函數。接收一個 fType1 類型的函數 fn 並返回一個調用 fn 的函數。裏面就包含有 defer/recover 機制,這在 13.3 節中有相應描述。

func errorHandler(fn fType1) fType1 {
    return func(a type1, b type2) {
        defer func() {
            if e, ok := recover().(error); ok {
                log.Printf(“run time panic: %v”, err)
            }
        }()
        fn(a, b)
    }
}

當錯誤發生時會 recover 並打印在日誌中;除了簡單的打印,應用也可以用 template 包(參見 15.7 節)爲用戶生成自定義的輸出。check() 函數會在所有的被調函數中調用,像這樣:

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)
    ...
}

通過這種機制,所有的錯誤都會被 recover,並且調用函數後的錯誤檢查代碼也被簡化爲調用 check(err) 即可。在這種模式下,不同的錯誤處理必須對應不同的函數類型;它們(錯誤處理)可能被隱藏在錯誤處理包內部。可選的更加通用的方式是用一個空接口類型的切片作爲參數和返回值。

我們會在 15.5 節的 web 應用中使用這種模式。

練習

練習 13.1recover_dividebyzero.go

用示例 13.3 中的編碼模式通過整數除以 0 觸發一個運行時 panic。

練習 13.2panic_defer.go

閱讀下面的完整程序。不要執行它,寫出程序的輸出結果。然後編譯執行並驗證你的預想。

// 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.

練習 13.3panic_defer_convint.go

寫一個 ConvertInt64ToInt 函數把 int64 值轉換爲 int 值,如果發生錯誤(提示:參見 4.5.2.1 節)就 panic。然後在函數 IntFromInt64 中調用這個函數並 recover,返回一個整數和一個錯誤。請測試這個函數!

鏈接

results matching ""

    No results matching ""