Go 語(yǔ)言的函數(shù)經(jīng)常使用兩個(gè)返回值來(lái)表示執(zhí)行是否成功:返回某個(gè)值以及 true 表示成功;返回零值(或 nil)和 false 表示失?。ǖ?4.4 節(jié))。當(dāng)不使用 true 或 false 的時(shí)候,也可以使用一個(gè) error 類型的變量來(lái)代替作為第二個(gè)返回值:成功執(zhí)行的話,error 的值為 nil,否則就會(huì)包含相應(yīng)的錯(cuò)誤信息(Go 語(yǔ)言中的錯(cuò)誤類型為 error: var err error
,我們將會(huì)在第 13 章進(jìn)行更多地討論)。這樣一來(lái),就很明顯需要用一個(gè) if 語(yǔ)句來(lái)測(cè)試執(zhí)行結(jié)果;由于其符號(hào)的原因,這樣的形式又稱之為 comma,ok 模式(pattern)。
在第 4.7 節(jié)的程序 string_conversion.go
中,函數(shù) strconv.Atoi
的作用是將一個(gè)字符串轉(zhuǎn)換為一個(gè)整數(shù)。之前我們忽略了相關(guān)的錯(cuò)誤檢查:
anInt, _ = strconv.Atoi(origStr)
如果 origStr 不能被轉(zhuǎn)換為整數(shù),anInt 的值會(huì)變成 0 而 _
無(wú)視了錯(cuò)誤,程序會(huì)繼續(xù)運(yùn)行。
這樣做是非常不好的:程序應(yīng)該在最接近的位置檢查所有相關(guān)的錯(cuò)誤,至少需要暗示用戶有錯(cuò)誤發(fā)生并對(duì)函數(shù)進(jìn)行返回,甚至中斷程序。
我們?cè)诘诙€(gè)版本中對(duì)代碼進(jìn)行了改進(jìn):
示例 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)
}
這是測(cè)試 err 變量是否包含一個(gè)真正的錯(cuò)誤(if err != nil
)的習(xí)慣用法。如果確實(shí)存在錯(cuò)誤,則會(huì)打印相應(yīng)的錯(cuò)誤信息然后通過(guò) return 提前結(jié)束函數(shù)的執(zhí)行。我們還可以使用攜帶返回值的 return 形式,例如 return err
。這樣一來(lái),函數(shù)的調(diào)用者就可以檢查函數(shù)執(zhí)行過(guò)程中是否存在錯(cuò)誤了。
習(xí)慣用法
value, err := pack1.Function1(param1)
if err != nil {
fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
return err
}
// 未發(fā)生錯(cuò)誤,繼續(xù)執(zhí)行:
由于本例的函數(shù)調(diào)用者屬于 main 函數(shù),所以程序會(huì)直接停止運(yùn)行。
如果我們想要在錯(cuò)誤發(fā)生的同時(shí)終止程序的運(yùn)行,我們可以使用 os
包的 Exit
函數(shù):
習(xí)慣用法
if err != nil {
fmt.Printf("Program stopping with error %v", err)
os.Exit(1)
}
(此處的退出代碼 1 可以使用外部腳本獲取到)
有時(shí)候,你會(huì)發(fā)現(xiàn)這種習(xí)慣用法被連續(xù)重復(fù)地使用在某段代碼中。
當(dāng)沒有錯(cuò)誤發(fā)生時(shí),代碼繼續(xù)運(yùn)行就是唯一要做的事情,所以 if 語(yǔ)句塊后面不需要使用 else 分支。
示例 2:我們嘗試通過(guò) os.Open
方法打開一個(gè)名為 name
的只讀文件:
f, err := os.Open(name)
if err != nil {
return err
}
doSomething(f) // 當(dāng)沒有錯(cuò)誤發(fā)生時(shí),文件對(duì)象被傳入到某個(gè)函數(shù)中
doSomething
練習(xí) 5.1 嘗試改寫 string_conversion2.go 中的代碼,要求使用 :=
方法來(lái)對(duì) err 進(jìn)行賦值,哪些地方可以被修改?
示例 3:可以將錯(cuò)誤的獲取放置在 if 語(yǔ)句的初始化部分:
習(xí)慣用法
if err := file.Chmod(0664); err != nil {
fmt.Println(err)
return err
}
示例 4:或者將 ok-pattern 的獲取放置在 if 語(yǔ)句的初始化部分,然后進(jìn)行判斷:
習(xí)慣用法
if value, ok := readData(); ok {
…
}
注意事項(xiàng)
如果您像下面一樣,沒有為多返回值的函數(shù)準(zhǔn)備足夠的變量來(lái)存放結(jié)果:
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)
}
您會(huì)得到一個(gè)編譯錯(cuò)誤:multiple-value mySqrt() in single-value context
。
正確的做法是:
t, ok := mySqrt(25.0)
if ok { fmt.Println(t) }
注意事項(xiàng) 2
當(dāng)您將字符串轉(zhuǎn)換為整數(shù)時(shí),且確定轉(zhuǎn)換一定能夠成功時(shí),可以將 Atoi
函數(shù)進(jìn)行一層忽略錯(cuò)誤的封裝:
func atoi (s string) (n int) {
n, _ = strconv.Atoi(s)
return
}
實(shí)際上,fmt
包(第 4.4.3 節(jié))最簡(jiǎn)單的打印函數(shù)也有 2 個(gè)返回值:
count, err := fmt.Println(x) // number of bytes printed, nil or 0, error
當(dāng)打印到控制臺(tái)時(shí),可以將該函數(shù)返回的錯(cuò)誤忽略;但當(dāng)輸出到文件流、網(wǎng)絡(luò)流等具有不確定因素的輸出對(duì)象時(shí),應(yīng)該始終檢查是否有錯(cuò)誤發(fā)生(另見練習(xí) 6.1b)。