## 5.14\. 錯誤處理
一些函數在調用后一般會返回一些錯誤的標志。在Go中我們可以用返回返回多個值來 方便地處理錯誤標志信息。一般情況下,錯誤都實現了os.Error 接口。
```
type Error interface {
String() string
}
```
庫的編寫者一般會在os.Error接口的基礎上擴展更多的信息,這樣函數調用者 可以知道錯誤的更多細節。例如:os.Open返回的是os.PathError 類型錯誤(里面已經包含最基本的錯誤接口)。
```
// PathError records an error and the operation and
// file path that caused it.
type PathError struct {
Op string // "open", "unlink", etc.
Path string // The associated file.
Error Error // Returned by the system call.
}
func (e *PathError) String() string {
return e.Op + " " + e.Path + ": " + e.Error.String()
}
```
PathError生成的String錯誤信息如下:
```
open /etc/passwx: no such file or directory
```
這個錯誤信息包含了要操作的文件名,對文件的具體操作,以及操作系統返回的錯誤信息。 這樣肯定比簡單輸出"no such file or directory"錯誤信息更有價值。
如果函數調用者想獲取錯誤的全部細節,那么需要將錯誤結果從基本的類型動態轉換到 更具體的錯誤類型。例如:下面的代碼將Error轉換為PathErrors 類型,因為后者的錯誤細細更加豐富。
```
for try := 0; try < 2; try++ {
file, err = os.Open(filename, os.O_RDONLY, 0)
if err == nil {
return
}
if e, ok := err.(*os.PathError); ok && e.Error == os.ENOSPC {
deleteTempFiles() // Recover some space.
continue
}
return
}
```
### 5.14.1\. Panic(怕死)
通常報錯的方式是給調用者一個多于的 os.Error 的返回值。經典的Read 方法是個出名的實例;它返回字節數和 os.Error 。但錯誤不可恢復則如何?有時程序就是不可再繼續了。
基于此目的,內部函數 panic 實際上會生成一個運行態錯誤來終止程序(但參見下節)。此函數取一個任意類型的參量 —— 通常是字串——在程序死掉時打印。它也用來指出某種不可能的事情發生了,例[http://code.google.com/p/ac-me/](http://code.google.com/p/ac-me/) 99如從永久循環中退出了。實際上,編輯器看到函數尾的 panic 會壓制通常的 return 語句檢查。
```
// A toy implementation of cube root using Newton's method.
func CubeRoot(x float64) float64 {
z := x/3 // Arbitrary intitial value
for i := 0; i < 1e6; i++ {
prevz := z
z -= (z*z*z-x) / (3*z*z)
if veryClose(z, prevz) {
return z
}
}
// A million iterations has not converged; something is wrong.
panic(fmt.Sprintf("CubeRoot(%g) did not converge", x))
}
```
這只是個例子,實際的庫函數要避免使用 panic 。如果某問題可被屏蔽或繞過,最好讓事情繼續而不是打死整個程序。一個可能的反例是在初始化時,如果某庫函數怎么都不能安排好自己,有理由 panic,可以這么說。
```
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
```
### 5.14.2\. Recover(回生)
當 panic 被叫,包括運行態錯誤例如數組下標越界或類型斷言失敗時,它會立即停止當前函數的執行,并開始退繞夠程的堆棧,隨之運行所有的延遲函數。如果退繞到夠程堆棧頂,程序死掉。但是,我們可以用內部函數 recover 重新控制夠程,恢復正常運行。
recover 的調用終止退繞并返回傳給 panic 的參量。因為退繞時只有延遲函數的代碼在運行,recover 只在延遲函數有用。
recover 的一個用途是在服務器內關閉失敗的夠程而不會殺死其它正在運行的夠程。
```
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Stderr("work failed:", err)
}
}()
do(work)
}
```
此例子中,如果 do(work) panic了,結果會記錄下,夠程會不擾人的干凈地退出。沒必要在延遲函數做其它的事;recover 的調用完全可以處理。
意有了這種復原的模式,do 函數(及其所有的調用)可以用 panic 從任何糟糕的情況里脫身。我們可用此概念簡化復雜軟件的出錯處理。我們看看 regexp 包里一個理想化的節選,它用局部的 Error 類型調用 panic 來報錯。 下面是 Error,error 方法,和 Compile 函數的定義:
```
// Error is the type of a parse error; it satisfies os.Error.
type Error string
func (e Error) String() string {
return string(e)
}
// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
panic(Error(err))
}
// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err os.Error) {
regexp = new(Regexp)
// doParse will panic if there is a parse error.
defer func() {
if e := recover(); e != nil {
regexp = nil // Clear return value.
err = e.(Error) // Will re-panic if not a parse error.
}
}()
return regexp.doParse(str), nil
}
```
如果 doParse panic了,復原塊會設置返回值為 nil ——延遲函數可以修改帶名的返回值。它然后通過斷定 err 的賦值是類型 Error 來檢查問題出自語法分析。如果不是,類型斷言會失敗,導致一個運行態錯誤,繼續堆棧退繞,就好像無事發生一樣。這個檢查意味著如果未曾預料的事情發生了,例如數組下標越界,代碼會失敗,盡管我們用了panic 和 recover 出來用戶觸發的錯誤。
有了這種出錯處理,error 方法能輕易的報錯,而不需擔心自己動手退繞堆棧。
這種有用的模式只應在一個包的內部使用。 Parse 將其內部的 panic 調用轉為 os.Error 值;不把 panic 暴露給客戶。這個好規則值得效法。
- 1. 關于本文
- 2. Go語言簡介
- 3. 安裝go環境
- 3.1. 簡介
- 3.2. 安裝C語言工具
- 3.3. 安裝Mercurial
- 3.4. 獲取代碼
- 3.5. 安裝Go
- 3.6. 編寫程序
- 3.7. 進一步學習
- 3.8. 更新go到新版本
- 3.9. 社區資源
- 3.10. 環境變量
- 4. Go語言入門
- 4.1. 簡介
- 4.2. Hello,世界
- 4.3. 分號(Semicolons)
- 4.4. 編譯
- 4.5. Echo
- 4.6. 類型簡介
- 4.7. 申請內存
- 4.8. 常量
- 4.9. I/O包
- 4.10. Rotting cats
- 4.11. Sorting
- 4.12. 打印輸出
- 4.13. 生成素數
- 4.14. Multiplexing
- 5. Effective Go
- 5.1. 簡介
- 5.2. 格式化
- 5.3. 注釋
- 5.4. 命名
- 5.5. 分號
- 5.6. 控制流
- 5.7. 函數
- 5.8. 數據
- 5.9. 初始化
- 5.10. 方法
- 5.11. 接口和其他類型
- 5.12. 內置
- 5.13. 并發
- 5.14. 錯誤處理
- 5.15. Web服務器
- 6. 如何編寫Go程序
- 6.1. 簡介
- 6.2. 社區資源
- 6.3. 新建一個包
- 6.4. 測試
- 6.5. 一個帶測試的演示包
- 7. Codelab: 編寫Web程序
- 7.1. 簡介
- 7.2. 開始
- 7.3. 數據結構
- 7.4. 使用http包
- 7.5. 基于http提供wiki頁面
- 7.6. 編輯頁面
- 7.7. template包
- 7.8. 處理不存在的頁面
- 7.9. 儲存頁面
- 7.10. 錯誤處理
- 7.11. 模板緩存
- 7.12. 驗證
- 7.13. 函數文本和閉包
- 7.14. 試試!
- 7.15. 其他任務
- 8. 針對C++程序員指南
- 8.1. 概念差異
- 8.2. 語法
- 8.3. 常量
- 8.4. Slices(切片)
- 8.5. 構造值對象
- 8.6. Interfaces(接口)
- 8.7. Goroutines
- 8.8. Channels(管道)
- 9. 內存模型
- 9.1. 簡介
- 9.2. Happens Before
- 9.3. 同步(Synchronization)
- 9.4. 錯誤的同步方式
- 10. 附錄
- 10.1. 命令行工具
- 10.2. 視頻和講座
- 10.3. Release History
- 10.4. Go Roadmap
- 10.5. 相關資源