Go編寫好的錯誤處理

編寫好的錯誤處理

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掉 守護進程 魄衅,就會幫我們的服務進程重新提起來。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末膏斤,一起剝皮案震驚了整個濱河市徐绑,隨后出現的幾起案子,更是在濱河造成了極大的恐慌莫辨,老刑警劉巖傲茄,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異沮榜,居然都是意外死亡盘榨,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門蟆融,熙熙樓的掌柜王于貴愁眉苦臉地迎上來草巡,“玉大人,你說我怎么就攤上這事型酥∩胶” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵弥喉,是天一觀的道長郁竟。 經常有香客問我,道長由境,這世上最難降的妖魔是什么棚亩? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮虏杰,結果婚禮上讥蟆,老公的妹妹穿的比我還像新娘。我一直安慰自己纺阔,他們只是感情好瘸彤,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著州弟,像睡著了一般钧栖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上婆翔,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天拯杠,我揣著相機與錄音,去河邊找鬼啃奴。 笑死潭陪,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播依溯,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼老厌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了黎炉?” 一聲冷哼從身側響起枝秤,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎慷嗜,沒想到半個月后淀弹,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡庆械,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年薇溃,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缭乘。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡沐序,死狀恐怖,靈堂內的尸體忽然破棺而出堕绩,到底是詐尸還是另有隱情策幼,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布奴紧,位于F島的核電站垄惧,受9級特大地震影響,放射性物質發(fā)生泄漏绰寞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一铣口、第九天 我趴在偏房一處隱蔽的房頂上張望滤钱。 院中可真熱鬧,春花似錦脑题、人聲如沸件缸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽他炊。三九已至,卻和暖如春已艰,著一層夾襖步出監(jiān)牢的瞬間痊末,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工哩掺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凿叠,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像盒件,于是被迫代替她去往敵國和親蹬碧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361