錯誤處理與測試
Go 沒有像 Java 和 .NET 那樣的 try/catch
異常機制:不能執行拋異常操作。但是有一套 defer-panic-and-recover
機制
錯誤處理
Go 有一個預先定義的 error 接口類型
type error interface {Error() string
}
errors 包中有一個 errorString 結構體實現了 error 接口。當程序處于錯誤狀態時可以用 os.Exit(1)
來中止運行。
定義錯誤
任何時候當你需要一個新的錯誤類型,都可以用 errors(必須先 import)包的 errors.New 函數接收合適的錯誤信息來創建,像下面這樣:
err := errors.New(“math - square root of negative number”)
包也可以用額外的方法(methods)定義特定的錯誤,比如 net.Error:
package net
type Error interface {Timeout() bool ? // Is the error a timeout?Temporary() bool // Is the error temporary?
}
用 fmt 創建錯誤對象
通常你想要返回包含錯誤參數的更有信息量的字符串,例如:可以用 fmt.Errorf() 來實現:它和 fmt.Printf () 完全一樣,接收有一個或多個格式占位符的格式化字符串和相應數量的占位變量。和打印信息不同的是它用信息生成錯誤對象。
比如在前面的平方根例子中使用:
if f < 0 {return 0, fmt.Errorf("math: square root of negative number %g", f)
}
運行時異常和 panic
當發生像數組下標越界或類型斷言失敗這樣的運行錯誤時,Go 運行時會觸發運行時 panic,伴隨著程序的崩潰拋出一個 runtime.Error 接口類型的值。這個錯誤值有個 RuntimeError() 方法用于區別普通錯誤。
panic 可以直接從代碼初始化:當錯誤條件(我們所測試的代碼)很嚴苛且不可恢復,程序不能繼續運行時,可以使用 panic 函數產生一個中止程序的運行時錯誤。panic 接收一個做任意類型的參數,通常是字符串,在程序死亡時被打印出來。Go 運行時負責中止程序并給出調試信息。
package main
?
import "fmt"
?
func main() {fmt.Println("Starting the program")panic("A severe error occurred: stopping the program!")fmt.Println("Ending the program")
}
從 panic 中恢復(Recover)
正如名字一樣,這個(recover)內建函數被用于從 panic 或 錯誤場景中恢復:讓程序可以從 panicking 重新獲得控制權,停止終止過程進而恢復正常執行。
recover 只能在 defer 修飾的函數中使用:用于取得 panic 調用中傳遞過來的錯誤值,如果是正常執行,調用 recover 會返回 nil,且沒有其它效果。
總結:panic 會導致棧被展開直到 defer 修飾的 recover () 被調用或者程序中止
這跟 Java 和 .NET 這樣的語言中的 catch 塊類似。 log 包實現了簡單的日志功能:默認的 log 對象向標準錯誤輸出中寫入并打印每條日志信息的日期和時間。除了 Println 和 Printf 函數,其它的致命性函數都會在寫完日志信息后調用 os.Exit (1),那些退出函數也是如此。而 Panic 效果的函數會在寫完日志信息后調用 panic;可以在程序必須中止或發生了臨界錯誤時使用它們,就像當 web 服務器不能啟動時那樣
自定義包中的錯誤處理和 panicking
這是所有自定義包實現者應該遵守的最佳實踐:
1)在包內部,總是應該從 panic 中 recover:不允許顯式的超出包范圍的 panic ()
2)向包的調用者返回錯誤值(而不是 panic)。
啟動外部命令和程序
os 包有一個 StartProcess 函數可以調用或啟動外部系統命令和二進制可執行文件;它的第一個參數是要運行的進程,第二個參數用來傳遞選項或參數,第三個參數是含有系統環境基本信息的結構體。
這個函數返回被啟動進程的 id(pid),或者啟動失敗返回錯誤
Go 中的單元測試和基準測試
_test 程序不會被普通的 Go 編譯器編譯,所以當放應用部署到生產環境時它們不會被部署;只有 gotest 會編譯所有的程序:普通程序和測試程序。
測試文件中必須導入 "testing" 包,并寫一些名字以 TestZzz 打頭的全局函數,這里的 Zzz 是被測試函數的字母描述,如 TestFmtInterface,TestPayEmployees 等。
測試的編寫規則:
Go 的測試必須按規則方式編寫,不然 go test 將無法正確定位測試代碼的位置,主要三點規則。
首先,測試代碼文件的命名必須是以 _test.go 結尾,比如上節中的文件名 math_tesh.go 并非隨意取的。
還有,代碼中的用例函數必須滿足匹配 TestXxx,比如 TestAbs。
關于 Xxx,簡單解釋一下,它主要傳達兩點含義,一是 Xxx 表示首個字符必須大寫或數字,簡單而言就是可確定單詞分隔,二是首字母后的字符可以是任意 Go 關鍵詞合法字符,如大小寫字母、下劃線、數字。
測試函數必須有這種形式的頭部:
func TestAbcde(t *testing.T)
T 是傳給測試函數的結構類型,用來管理測試狀態,支持格式化測試日志,如 t.Log,t.Error,t.ErrorF 等。在函數的結尾把輸出跟想要的結果對比,如果不等就打印一個錯誤。成功的測試則直接返回。
用下面這些函數來通知測試失敗:
1)func (t *T) Fail()
標記測試函數為失敗,然后繼續執行(剩下的測試)。
2)func (t *T) FailNow()
標記測試函數為失敗并中止執行;文件中別的測試也被略過,繼續執行下一個文件。
3)func (t *T) Log(args ...interface{})
args 被用默認的格式格式化并打印到錯誤日志中。
4)func (t *T) Fatal(args ...interface{})
結合 先執行 3),然后執行 2)的效果。
package even
?
import "testing"
?
func TestEven(t *testing.T) {if !Even(10) {t.Log(" 10 must be even!")t.Fail()}if Even(7) {t.Log(" 7 is not even!")t.Fail()}
?
}
?
func TestOdd(t *testing.T) {if !Odd(11) {t.Log(" 11 must be odd!")t.Fail()}if Odd(10) {t.Log(" 10 is not odd!")t.Fail()}
}