在Go中有一部分函數(shù)總是能成功的運(yùn)行。比如strings.Contains和strconv.FormatBool函數(shù),對(duì)各種可能的輸入都做了良好的處理,使得運(yùn)行時(shí)幾乎不會(huì)失敗,除非遇到災(zāi)難性的、不可預(yù)料的情況,比如運(yùn)行時(shí)的內(nèi)存溢出。導(dǎo)致這種錯(cuò)誤的原因很復(fù)雜,難以處理,從錯(cuò)誤中恢復(fù)的可能性也很低。
還有一部分函數(shù)只要輸入的參數(shù)滿(mǎn)足一定條件,也能保證運(yùn)行成功。比如time.Date函數(shù),該函數(shù)將年月日等參數(shù)構(gòu)造成time.Time對(duì)象,除非最后一個(gè)參數(shù)(時(shí)區(qū))是nil。這種情況下會(huì)引發(fā)panic異常。panic是來(lái)自被調(diào)函數(shù)的信號(hào),表示發(fā)生了某個(gè)已知的bug。一個(gè)良好的程序永遠(yuǎn)不應(yīng)該發(fā)生panic異常。
對(duì)于大部分函數(shù)而言,永遠(yuǎn)無(wú)法確保能否成功運(yùn)行。這是因?yàn)殄e(cuò)誤的原因超出了程序員的控制。舉個(gè)例子,任何進(jìn)行I/O操作的函數(shù)都會(huì)面臨出現(xiàn)錯(cuò)誤的可能,只有沒(méi)有經(jīng)驗(yàn)的程序員才會(huì)相信讀寫(xiě)操作不會(huì)失敗,即使是簡(jiǎn)單的讀寫(xiě)。因此,當(dāng)本該可信的操作出乎意料的失敗后,我們必須弄清楚導(dǎo)致失敗的原因。
在Go的錯(cuò)誤處理中,錯(cuò)誤是軟件包API和應(yīng)用程序用戶(hù)界面的一個(gè)重要組成部分,程序運(yùn)行失敗僅被認(rèn)為是幾個(gè)預(yù)期的結(jié)果之一。
錯(cuò)誤處理策略
首先,也是最常用的方式是傳播錯(cuò)誤。這意味著函數(shù)中某個(gè)子程序的失敗,會(huì)變成該函數(shù)的失敗。下面,我們以5.3節(jié)的findLinks函數(shù)作為例子。如果findLinks對(duì)http.Get的調(diào)用失敗,findLinks會(huì)直接將這個(gè)HTTP錯(cuò)誤返回給調(diào)用者:
resp, err := http.Get(url)
if err != nil{
return nil, err
}
當(dāng)對(duì)html.Parse的調(diào)用失敗時(shí),findLinks不會(huì)直接返回html.Parse的錯(cuò)誤,因?yàn)槿鄙賰蓷l重要信息:
1、錯(cuò)誤發(fā)生在解析器;
2、url已經(jīng)被解析。
這些信息有助于錯(cuò)誤的處理,findLinks會(huì)構(gòu)造新的錯(cuò)誤信息返回給調(diào)用者:
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)
}
讓我們來(lái)看看處理錯(cuò)誤的第二種策略。如果錯(cuò)誤的發(fā)生是偶然性的,或由不可預(yù)知的問(wèn)題導(dǎo)致的。一個(gè)明智的選擇是重新嘗試失敗的操作。在重試時(shí),我們需要限制重試的時(shí)間間隔或重試的次數(shù),防止無(wú)限制的重試。
// WaitForServer attempts to contact the server of a URL.
// It tries for one minute using exponential back-off.
// It reports an error if all attempts fail.
func WaitForServer(url string) error {
const timeout = 1 * time.Minute
deadline := time.Now().Add(timeout)
for tries := 0; time.Now().Before(deadline); tries++ {
_, err := http.Head(url)
if err == nil {
return nil // success
}
log.Printf("server not responding (%s);retrying…", err)
time.Sleep(time.Second << uint(tries)) // exponential back-off
}
return fmt.Errorf("server %s failed to respond after %s", url, timeout)
}