5.2 測試多返回值函數的錯誤

Go 語言的函數經常使用兩個返回值來表示執行是否成功:返回某個值以及 true 表示成功;返回零值(或 nil)和 false 表示失敗(第 4.4 節)。當不使用 true 或 false 的時候,也可以使用一個 error 類型的變量來代替作爲第二個返回值:成功執行的話,error 的值爲 nil,否則就會包含相應的錯誤信息(Go 語言中的錯誤類型爲 error: var err error,我們將會在第 13 章進行更多地討論)。這樣一來,就很明顯需要用一個 if 語句來測試執行結果;由於其符號的原因,這樣的形式又稱之爲 comma,ok 模式(pattern)。

在第 4.7 節的程序 string_conversion.go 中,函數 strconv.Atoi 的作用是將一個字符串轉換爲一個整數。之前我們忽略了相關的錯誤檢查:

anInt, _ = strconv.Atoi(origStr)

如果 origStr 不能被轉換爲整數,anInt 的值會變成 0 而 _ 無視了錯誤,程序會繼續運行。

這樣做是非常不好的:程序應該在最接近的位置檢查所有相關的錯誤,至少需要暗示用戶有錯誤發生並對函數進行返回,甚至中斷程序。

我們在第二個版本中對代碼進行了改進:

示例 1:

示例 5.3 string_conversion2.go

package main

import (
    "fmt"
    "strconv"
)

func main() {
    var orig string = "ABC"
    // var an int
    var newS string
    // var err error

    fmt.Printf("The size of ints is: %d\n", strconv.IntSize)      
    // anInt, err = strconv.Atoi(origStr)
    an, err := strconv.Atoi(orig)
    if err != nil {
        fmt.Printf("orig %s is not an integer - exiting with error\n", orig)
        return
    } 
    fmt.Printf("The integer is %d\n", an)
    an = an + 5
    newS = strconv.Itoa(an)
    fmt.Printf("The new string is: %s\n", newS)
}

這是測試 err 變量是否包含一個真正的錯誤(if err != nil)的習慣用法。如果確實存在錯誤,則會打印相應的錯誤信息然後通過 return 提前結束函數的執行。我們還可以使用攜帶返回值的 return 形式,例如 return err。這樣一來,函數的調用者就可以檢查函數執行過程中是否存在錯誤了。

習慣用法

value, err := pack1.Function1(param1)
if err != nil {
    fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
    return err
}
// 未發生錯誤,繼續執行:

由於本例的函數調用者屬於 main 函數,所以程序會直接停止運行。

如果我們想要在錯誤發生的同時終止程序的運行,我們可以使用 os 包的 Exit 函數:

習慣用法

if err != nil {
    fmt.Printf("Program stopping with error %v", err)
    os.Exit(1)
}

(此處的退出代碼 1 可以使用外部腳本獲取到)

有時候,你會發現這種習慣用法被連續重複地使用在某段代碼中。

當沒有錯誤發生時,代碼繼續運行就是唯一要做的事情,所以 if 語句塊後面不需要使用 else 分支。

示例 2:我們嘗試通過 os.Open 方法打開一個名爲 name 的只讀文件:

f, err := os.Open(name)
if err != nil {
    return err
}
doSomething(f) // 當沒有錯誤發生時,文件對象被傳入到某個函數中
doSomething

練習 5.1 嘗試改寫 string_conversion2.go 中的代碼,要求使用 := 方法來對 err 進行賦值,哪些地方可以被修改?

示例 3:可以將錯誤的獲取放置在 if 語句的初始化部分:

習慣用法

if err := file.Chmod(0664); err != nil {
    fmt.Println(err)
    return err
}

示例 4:或者將 ok-pattern 的獲取放置在 if 語句的初始化部分,然後進行判斷:

習慣用法

if value, ok := readData(); ok {
…
}

注意事項

如果您像下面一樣,沒有爲多返回值的函數準備足夠的變量來存放結果:

func mySqrt(f float64) (v float64, ok bool) {
    if f < 0 { return } // error case
    return math.Sqrt(f),true
}

func main() {
    t := mySqrt(25.0)
    fmt.Println(t)
}

您會得到一個編譯錯誤:multiple-value mySqrt() in single-value context

正確的做法是:

t, ok := mySqrt(25.0)
if ok { fmt.Println(t) }

注意事項 2

當您將字符串轉換爲整數時,且確定轉換一定能夠成功時,可以將 Atoi 函數進行一層忽略錯誤的封裝:

func atoi (s string) (n int) {
    n, _ = strconv.Atoi(s)
    return
}

實際上,fmt 包(第 4.4.3 節)最簡單的打印函數也有 2 個返回值:

count, err := fmt.Println(x) // number of bytes printed, nil or 0, error

當打印到控制檯時,可以將該函數返回的錯誤忽略;但當輸出到文件流、網絡流等具有不確定因素的輸出對象時,應該始終檢查是否有錯誤發生(另見練習 6.1b)。

鏈接

results matching ""

    No results matching ""