Golang 學習筆記(07)—— 錯誤及異常處理

本文為轉(zhuǎn)載倍宾,原文:Golang 學習筆記(07)—— 錯誤及異常處理

Golang

基礎知識

錯誤指的是可能出現(xiàn)問題的地方出現(xiàn)了問題高职,比如打開一個文件時失敗初厚,這種情況在人們的意料之中 孙技;而異常指的是不應該出現(xiàn)問題的地方出現(xiàn)了問題牵啦,比如引用了空指針,這種情況在人們的意料之外楞件⊥两可見,錯誤是業(yè)務過程的一部分泪酱,而異常不是 还最。

Golang中引入error接口類型作為錯誤處理的標準模式拓轻,如果函數(shù)要返回錯誤,則返回值類型列表中肯定包含error勿锅。error處理過程類似于C語言中的錯誤碼枣氧,可逐層返回作瞄,直到被處理宗挥。

Golang中引入兩個內(nèi)置函數(shù)panic和recover來觸發(fā)和終止異常處理流程种蝶,同時引入關鍵字defer來延遲執(zhí)行defer后面的函數(shù)。

一直等到包含defer語句的函數(shù)執(zhí)行完畢時搪桂,延遲函數(shù)(defer后的函數(shù))才會被執(zhí)行盯滚,而不管包含defer語句的函數(shù)是通過return的正常結(jié)束魄藕,還是由于panic導致的異常結(jié)束。你可以在一個函數(shù)中執(zhí)行多條defer語句话瞧,它們的執(zhí)行順序與聲明順序相反交排。

當程序運行時,如果遇到引用空指針处坪、下標越界或顯式調(diào)用panic函數(shù)等情況稻薇,則先觸發(fā)panic函數(shù)的執(zhí)行,然后調(diào)用延遲函數(shù)胶征。調(diào)用者繼續(xù)傳遞panic塞椎,因此該過程一直在調(diào)用棧中重復發(fā)生:函數(shù)停止執(zhí)行,調(diào)用延遲執(zhí)行函數(shù)等睛低。如果一路在延遲函數(shù)中沒有recover函數(shù)的調(diào)用案狠,則會到達該協(xié)程的起點,該協(xié)程結(jié)束钱雷,然后終止其他所有協(xié)程骂铁,包括主協(xié)程。

Golang錯誤和異常是可以互相轉(zhuǎn)換的:

  • 錯誤轉(zhuǎn)異常罩抗,比如程序邏輯上嘗試請求某個URL拉庵,最多嘗試三次套蒂,嘗試三次的過程中請求失敗是錯誤钞支,嘗試完第三次還不成功的話,失敗就被提升為異常了操刀。
  • 異常轉(zhuǎn)錯誤烁挟,比如panic觸發(fā)的異常被recover恢復后,將返回值中error類型的變量進行賦值骨坑,以便上層函數(shù)繼續(xù)走錯誤處理流程撼嗓。

實例

package main

import (
    "errors"
    "fmt"
)

func main(){
    defer func(){
        if err := recover(); err != nil{ //捕捉異常并處理
            fmt.Println("err: ", err)
        }
    }()

    if num, err := delive(20, -5); err == nil{
        fmt.Printf("%f / %f = %f\n", 20.0, -5.0, num)
    }else{
        fmt.Println(err)
    }

    if num, err := delive(20, 0); err == nil{
        fmt.Printf("%f / %f = %f\n", 20.0, 0.0, num)
    }else{
        fmt.Println(err)
    }
}

func delive(numA, numB float32) (float32, error){
    if numB < 0{
        return 0, errors.New("被除數(shù)不能為負數(shù)")
    }else if numB == 0{
        panic("被除數(shù)不能為0") //拋出異常
    }else{
        return numA/numB, nil
    }
}
運行結(jié)果

error 類型實際上是抽象了 Error() 方法的 error 接口,Golang 使用該接口進行標準的錯誤處理欢唾。
Go中可以拋出一個panic的異常且警,然后在defer中通過recover捕獲這個異常,然后正常處理礁遣。

error使用場景

  1. 失敗原因只有一個時振湾,不應該使用error
    當函數(shù)失敗的原因只有一個,所以返回值的類型應該為bool亡脸,而不是error押搪。大多數(shù)情況树酪,導致失敗的原因不止一種,尤其是對I/O操作而言大州,用戶需要了解更多的錯誤信息续语,這時的返回值類型不再是簡單的bool,而是error厦画。

  2. 沒有失敗時疮茄,不使用error
    error在Golang中是如此的流行,以至于很多人設計函數(shù)時不管三七二十一都使用error根暑,即使沒有一個失敗原因力试。當沒有失敗原因時,使用無返回的函數(shù)會更加合理排嫌。

  3. error應放在返回值類型列表的最后
    對于返回值類型error畸裳,用來傳遞錯誤信息,在Golang中通常放在最后一個淳地。bool作為返回值類型時也一樣怖糊。

  4. 錯誤值統(tǒng)一定義,而不是跟著感覺走
    很多人寫代碼時颇象,到處return errors.New(value)伍伤,而錯誤value在表達同一個含義時也可能形式不同。
    這使得相同的錯誤value撒在一大片代碼里遣钳,當上層函數(shù)要對特定錯誤value進行統(tǒng)一處理時扰魂,需要漫游所有下層代碼,以保證錯誤value統(tǒng)一蕴茴,不幸的是有時會有漏網(wǎng)之魚劝评,而且這種方式嚴重阻礙了錯誤value的重構。
    于是荐开,我們可以在Golang的每個包中增加一個錯誤對象定義文件付翁,如下所示:

var ERR_EOF = errors.New("EOF")
var ERR_CLOSED_PIPE = errors.New("io: read/write on closed pipe")
var ERR_NO_PROGRESS = errors.New("multiple Read calls return no data or error")
var ERR_SHORT_BUFFER = errors.New("short buffer")
var ERR_SHORT_WRITE = errors.New("short write")
var ERR_UNEXPECTED_EOF = errors.New("unexpected EOF")
  1. 錯誤逐層傳遞時简肴,層層都加日志
    層層都加日志非常方便故障定位晃听。

  2. 錯誤處理使用defer
    我們一般通過判斷error的值來處理錯誤,如果當前操作失敗砰识,需要將本函數(shù)中已經(jīng)create的資源destroy掉能扒,示例代碼如下:

func deferDemo() error {
    err := createResource1()
    if err != nil {
        return ERR_CREATE_RESOURCE1_FAILED
    }
    err = createResource2()
    if err != nil {
        destroyResource1()
        return ERR_CREATE_RESOURCE2_FAILED
    }

    err = createResource3()
    if err != nil {
        destroyResource1()
        destroyResource2()
        return ERR_CREATE_RESOURCE3_FAILED
    }

    err = createResource4()
    if err != nil {
        destroyResource1()
        destroyResource2()
        destroyResource3()
        return ERR_CREATE_RESOURCE4_FAILED
    }
    return nil
}

當Golang的代碼執(zhí)行時鹉胖,如果遇到defer的閉包調(diào)用酒朵,則壓入堆棧。當函數(shù)返回時永淌,會按照后進先出的順序調(diào)用閉包膨处。
對于閉包的參數(shù)是值傳遞见秤,而對于外部變量卻是引用傳遞砂竖,所以閉包中的外部變量err的值就變成外部函數(shù)返回時最新的err值。
根據(jù)這個結(jié)論鹃答,我們重構上面的示例代碼:

func deferDemo() error {
    err := createResource1()
    if err != nil {
        return ERR_CREATE_RESOURCE1_FAILED
    }
    defer func() {
        if err != nil {
            destroyResource1()
        }
    }()
    err = createResource2()
    if err != nil {
        return ERR_CREATE_RESOURCE2_FAILED
    }
    defer func() {
        if err != nil {
            destroyResource2()
        }
    }()

    err = createResource3()
    if err != nil {
        return ERR_CREATE_RESOURCE3_FAILED
    }
    defer func() {
        if err != nil {
            destroyResource3()
        }
    }()

    err = createResource4()
    if err != nil {
        return ERR_CREATE_RESOURCE4_FAILED
    }
    return nil
}
  1. 當上層函數(shù)不關心錯誤時乎澄,建議不返回error
    對于一些資源清理相關的函數(shù)(destroy/delete/clear),如果子函數(shù)出錯测摔,打印日志即可置济,而無需將錯誤進一步反饋到上層函數(shù),因為一般情況下锋八,上層函數(shù)是不關心執(zhí)行結(jié)果的浙于,或者即使關心也無能為力,于是我們建議將相關函數(shù)設計為不返回error挟纱。

異常的使用場景

  1. 在程序開發(fā)階段羞酗,堅持速錯
    速錯,簡單來講就是“讓它掛”樊销,只有掛了你才會第一時間知道錯誤整慎。在早期開發(fā)以及任何發(fā)布階段之前,最簡單的同時也可能是最好的方法是調(diào)用panic函數(shù)來中斷程序的執(zhí)行以強制發(fā)生錯誤围苫,使得該錯誤不會被忽略裤园,因而能夠被盡快修復。

  2. 在程序部署后剂府,應恢復異常避免程序終止
    一旦Golang程序部署后拧揽,在任何情況下發(fā)生的異常都不應該導致程序異常退出,我們在上層函數(shù)中加一個延遲執(zhí)行的recover調(diào)用來達到這個目的腺占,并且是否進行recover需要根據(jù)環(huán)境變量或配置文件來定淤袜,默認需要recover。
    我們在調(diào)用recover的延遲函數(shù)中以最合理的方式響應該異常:

打印堆棧的異常調(diào)用信息和關鍵的業(yè)務信息衰伯,以便這些問題保留可見铡羡;
將異常轉(zhuǎn)換為錯誤,以便調(diào)用者讓程序恢復到健康狀態(tài)并繼續(xù)安全運行意鲸。

  1. 對于不應該出現(xiàn)的分支烦周,使用異常處理
    當某些不應該發(fā)生的場景發(fā)生時,我們就應該調(diào)用panic函數(shù)來觸發(fā)異常怎顾。比如读慎,當程序到達了某條邏輯上不可能到達的路徑

  2. 針對入?yún)⒉粦撚袉栴}的函數(shù),使用panic設計
    對于同時支持用戶輸入場景和硬編碼場景的情況槐雾,一般支持硬編碼場景的函數(shù)是對支持用戶輸入場景函數(shù)的包裝夭委。
    對于只支持硬編碼單一場景的情況,函數(shù)設計時直接使用panic募强,即返回值類型列表中不會有error株灸,這使得函數(shù)的調(diào)用處理非常方便

轉(zhuǎn)載請注明出處:
Golang 學習筆記(07)—— 錯誤及異常處理

目錄
上一節(jié):Golang 學習筆記(06)—— 多線程
下一節(jié):Golang 學習筆記(08)—— 文件操作

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末崇摄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子慌烧,更是在濱河造成了極大的恐慌配猫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杏死,死亡現(xiàn)場離奇詭異泵肄,居然都是意外死亡,警方通過查閱死者的電腦和手機淑翼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門腐巢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玄括,你說我怎么就攤上這事冯丙。” “怎么了遭京?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵胃惜,是天一觀的道長。 經(jīng)常有香客問我哪雕,道長船殉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任斯嚎,我火速辦了婚禮利虫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘堡僻。我一直安慰自己糠惫,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布钉疫。 她就那樣靜靜地躺著硼讽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牲阁。 梳的紋絲不亂的頭發(fā)上固阁,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音咨油,去河邊找鬼您炉。 笑死柒爵,一個胖子當著我的面吹牛役电,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棉胀,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼法瑟,長吁一口氣:“原來是場噩夢啊……” “哼冀膝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霎挟,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤窝剖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酥夭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赐纱,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年熬北,在試婚紗的時候發(fā)現(xiàn)自己被綠了疙描。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡讶隐,死狀恐怖起胰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巫延,我是刑警寧澤效五,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站炉峰,受9級特大地震影響畏妖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疼阔,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一瓜客、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竿开,春花似錦谱仪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至列荔,卻和暖如春敬尺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贴浙。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工砂吞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人崎溃。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓蜻直,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子概而,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 序言 錯誤和異常是兩個不同的概念呼巷,非常容易混淆。很多程序員習慣將一切非正常情況都看做錯誤赎瑰,而不區(qū)分錯誤和異常王悍,即使...
    _張曉龍_閱讀 78,627評論 16 136
  • 序言 Golang遵循“少即是多”的設計哲學,同時又支持閉包(Closure)餐曼,那么閉包對于Golang來說肯定有...
    _張曉龍_閱讀 4,441評論 7 26
  • 前言 本規(guī)范是針對 Go 語言的編碼規(guī)范压储,目的是為了統(tǒng)一項目的編碼風格,提高源程序的可讀性源譬、可靠性和可重用性渠脉,從而...
    _張曉龍_閱讀 1,955評論 5 21
  • golang中沒有類似Java/C++等面向?qū)ο缶幊陶Z言中的try...catch...finally...語句結(jié)...
    CodingTech閱讀 2,510評論 1 12
  • 海帶絲上一點青,酸爽美味胃中行瓶佳。 蒸魚搭配一片蔥芋膘,色香味全勸君醒。 家中菜肴一大廳霸饲,賓客滿座坐門迎为朋。 米飯滿上一兩...
    快樂的Alina閱讀 315評論 6 2