## 5.4. 錯誤


在Go中有一部分函數總是能成功的運行。比如string.Contains和strconv.FormatBool函數，對各種可能的輸入都做了良好的處理，使得運行時幾乎不會失敗，除非遇到災難性的、不可預料的情況，比如運行時的內存溢出。導致這種錯誤的原因很複雜，難以處理，從錯誤中恢複的可能性也很低。

還有一部分函數隻要輸入的參數滿足一定條件，也能保證運行成功。比如time.Date函數，該函數將年月日等參數構造成time.Time對象，除非最後一個參數（時區）是nil。這種情況下會引發panic異常。panic是來自被調函數的信號，表示發生了某個已知的bug。一個良好的程序永遠不應該發生panic異常。

對於大部分函數而言，永遠無法確保能否成功運行。這是因爲錯誤的原因超出了程序員的控製。舉個例子，任何進行I/O操作的函數都會面臨出現錯誤的可能，隻有沒有經驗的程序員才會相信讀寫操作不會失敗，卽時是簡單的讀寫。因此，當本該可信的操作出乎意料的失敗後，我們必鬚弄清楚導致失敗的原因。

在Go的錯誤處理中，錯誤是軟件包API和應用程序用戶界面的一個重要組成部分，程序運行失敗僅被認爲是幾個預期的結果之一。

對於那些將運行失敗看作是預期結果的函數，它們會返迴一個額外的返迴值，通常是最後一個，來傳遞錯誤信息。如果導致失敗的原因隻有一個，額外的返迴值可以是一個布爾值，通常被命名爲ok。比如，cache.Lookup失敗的唯一原因是key不存在，那麽代碼可以按照下面的方式組織：

```Go
value, ok := cache.Lookup(key)
if !ok {
	// ...cache[key] does not exist…
}
```

通常，導致失敗的原因不止一種，尤其是對I/O操作而言，用戶需要了解更多的錯誤信息。因此，額外的返迴值不再是簡單的布爾類型，而是error類型。

內置的error是接口類型。我們將在第七章了解接口類型的含義，以及它對錯誤處理的影響。現在我們隻需要明白error類型可能是nil或者non-nil。nil意味着函數運行成功，non-nil表示失敗。對於non-nil的error類型,我們可以通過調用error的Error函數或者輸出函數獲得字符串類型的錯誤信息。

```Go
fmt.Println(err)
fmt.Printf("%v", err)
```

通常，當函數返迴non-nil的error時，其他的返迴值是未定義的(undefined),這些未定義的返迴值應該被忽略。然而，有少部分函數在發生錯誤時，仍然會返迴一些有用的返迴值。比如，當讀取文件發生錯誤時，Read函數會返迴可以讀取的字節數以及錯誤信息。對於這種情況，正確的處理方式應該是先處理這些不完整的數據，再處理錯誤。因此對函數的返迴值要有清晰的説明，以便於其他人使用。

在Go中，函數運行失敗時會返迴錯誤信息，這些錯誤信息被認爲是一種預期的值而非異常（exception），這使得Go有别於那些將函數運行失敗看作是異常的語言。雖然Go有各種異常機製，但這些機製僅被使用在處理那些未被預料到的錯誤，卽bug，而不是那些在健壯程序中應該被避免的程序錯誤。對於Go的異常機製我們將在5.9介紹。

Go這樣設計的原因是由於對於某個應該在控製流程中處理的錯誤而言，將這個錯誤以異常的形式拋出會混亂對錯誤的描述，這通常會導致一些糟糕的後果。當某個程序錯誤被當作異常處理後，這個錯誤會將堆棧根據信息返迴給終端用戶，這些信息複雜且無用，無法幫助定位錯誤。

正因此，Go使用控製流機製（如if和return）處理異常，這使得編碼人員能更多的關註錯誤處理。

{% include "./ch5-04-1.md" %}

{% include "./ch5-04-2.md" %}
