編寫好的錯誤處理
Go的錯誤機制:
沒有異常機制
-
error
類型實現了error
接口type error interface { Error() string }
-
可以通過
errors.News
來快速創(chuàng)建錯誤實例errors.News("n must be in the range [0,10]")
拿Fibonacci舉例:
func GetFibonacci(n int) []int {
fibList := []int{1, 2}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList
}
func TestGetFibonacci(t *testing.T) {
t.Log(GetFibonacci(10))
t.Log(GetFibonacci(-10))
/** 運行結果
=== RUN TestGetFibonacci
TestGetFibonacci: err_test.go:15: [1 2 3 5 8 13 21 34 55 89]
TestGetFibonacci: err_test.go:21: [1 2]
--- PASS: TestGetFibonacci (0.00s)
*/
}
可以看到沒有對入參進行校驗
現在做下校驗:
func GetFibonacci(n int) ([]int, error) {
if n < 2 || n > 100 {
return nil, errors.New("n should be in [2,100]")
}
fibList := []int{1, 2}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
func TestGetFibonacci(t *testing.T) {
// 如果有錯誤進行錯誤輸出
if v, err := GetFibonacci(-10); err != nil {
t.Error(err)
} else {
t.Log(v)
}
/** 運行結果
=== RUN TestGetFibonacci
TestGetFibonacci: err_test.go:22: n should be in [2,100]
--- FAIL: TestGetFibonacci (0.00s)
*/
}
假設現在有個需求泳炉,返回的值是太小了還是太大了熟掂,返回不同的錯誤,最簡單的方法直接改造GetFibonacci
:
func GetFibonacci(n int) ([]int, error) {
if n < 2 {
return nil, errors.New("n should be not less than 2")
}
if n > 100 {
return nil, errors.New("n should be not larger than 100")
}
fibList := []int{1, 2}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
如果區(qū)分錯誤類型啊片,依靠字符串去匹配簡直太麻煩還容易出錯,最常見的解決方法茬底,定義兩個預置的錯誤:
var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")
func GetFibonacci(n int) ([]int, error) {
if n < 2 {
return nil, LessThanTwoError
}
if n > 100 {
return nil, LargerThenHundredError
}
fibList := []int{1, 2}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
func TestGetFibonacci(t *testing.T) {
// 如果有錯誤進行錯誤輸出
if v, err := GetFibonacci(-10); err != nil {
// 假如調用者需要判斷錯誤的就比較簡單了
if err == LessThanTwoError {
fmt.Println("It is less.")
}
t.Error(err)
} else {
t.Log(v)
}
/** 運行結果
=== RUN TestGetFibonacci
It is less.
TestGetFibonacci: err_test.go:36: n should be not less than 2
--- FAIL: TestGetFibonacci (0.00s)
*/
}
總結:
定義不同的錯誤變量悴灵,以便于判斷錯誤類型
及早失敗术吗,避免嵌套,提高代碼可讀性
panic和recover
panic
panic:
-
panic
用于不可以恢復的錯誤 -
panic
退出前會執(zhí)行defer
指定的內容
panic vs. os.Exit:
-
os.Exit
退出時不會調用defer
指定的函數 -
os.Exit
退出時不輸出當前調用棧的信息
func TestExit(t *testing.T) {
fmt.Println("Start")
os.Exit(-1)
/** 運行結果
=== RUN TestExit
Start
Process finished with exit code 1
*/
}
func TestPanic(t *testing.T) {
defer func() {
fmt.Println("Finally!")
}()
fmt.Println("Start")
panic(errors.New("Something wrong!"))
/** 運行結果:
=== RUN TestPanic
Start
Finally!
--- FAIL: TestPanic (0.00s)
panic: Something wrong! [recovered]
panic: Something wrong!
goroutine 6 [running]:
testing.tRunner.func1.1(0x1119860, 0xc000046510)
/usr/local/Cellar/go/1.14.2_1/libexec/src/testing/testing.go:940 +0x2f5
testing.tRunner.func1(0xc00011a120)
/usr/local/Cellar/go/1.14.2_1/libexec/src/testing/testing.go:943 +0x3f9
panic(0x1119860, 0xc000046510)
/usr/local/Cellar/go/1.14.2_1/libexec/src/runtime/panic.go:969 +0x166
command-line-arguments.TestPanic(0xc00011a120)
/Users/gaobinzhan/Documents/Go/learning/src/test/err_test.go:65 +0xd7
testing.tRunner(0xc00011a120, 0x114afa0)
/usr/local/Cellar/go/1.14.2_1/libexec/src/testing/testing.go:991 +0xdc
created by testing.(*T).Run
/usr/local/Cellar/go/1.14.2_1/libexec/src/testing/testing.go:1042 +0x357
Process finished with exit code 1
*/
}
recover
大家在寫c++或者php代碼的時候情萤,總有一種習慣不希望這個程序被中斷或者退出鸭蛙,用來捕獲。
php代碼:
try {
} catch (\Throwable $throwable) {
}
c++ 代碼:
try{
...
}catch(...){
}
go代碼:
defer func(){
if err := recover(); err != nil {
// 恢復錯誤
}
}()
func TestRecover(t *testing.T) {
defer func() {
if err := recover(); err != nil {
// 沒有寫錯誤恢復 只是打印出來了
fmt.Println("recovered from", err)
}
}()
fmt.Println("Start")
panic(errors.New("Something wrong!"))
/** 運行結果:
=== RUN TestRecover
Start
recovered from Something wrong!
--- PASS: TestRecover (0.00s)
*/
}
最常見的"錯誤恢復":
defer func() {
if err := recover(); err != nil {
log.Error("recovered panic",err)
}
}()
當心筋岛!recover
成為惡魔:
- 形成僵尸服務進程娶视,導致 health check 失效。
- “Let it Crash!” 往往是我們恢復不確定性錯誤的最好方法睁宰。
就如上常見的“錯誤恢復”只是記錄了一下肪获,這樣的恢復方式是非常危險的。
一定要當心我們自己 recover
在做的事柒傻,因為我們 recover
的時候并不去檢測錯誤到底發(fā)生了什么錯誤孝赫,而是簡單的記錄了一下或者忽略。
這時候可能是系統(tǒng)里面的某些核心資源已經消耗完了红符,我們這樣把它強制恢復掉青柄,其實系統(tǒng)依然不能夠正常地工作的,還是導致我們的一些健康檢查程序 health check 沒有辦法檢查出當前系統(tǒng)的問題预侯。
因為很多的這種 health check 只是檢查當前的系統(tǒng)進程在還是不在致开,因為我們的進程是在的,所以就會導致一種僵尸服務進程萎馅,它好像活著双戳,但它也不能提供服務。
這種情況下個人認為倒不如采用一種可恢復的設計模式其中的一種叫 Let it Crash
糜芳,干脆 Crash
掉飒货,一旦Crash
掉 守護進程 魄衅,就會幫我們的服務進程重新提起來。