[翻譯]Go的Defer丐黄、Panic和Recover

dont-panic.png

簡(jiǎn)書不維護(hù)了,歡迎關(guān)注我的知乎:波羅學(xué)的個(gè)人主頁(yè)

翻譯自:https://blog.golang.org/defer-panic-and-recover

Go有和其他語言一樣常見的流程控制語句:if, for, switch, goto靶端。同時(shí)也有g(shù)o表達(dá)式來實(shí)現(xiàn)在不同的goroutine中運(yùn)行代碼(并發(fā))球凰。而今天我們將討論的是go的異常控制流程:defer神僵、panic和recover酌心。

Defer

defer語句會(huì)將函數(shù)推入到一個(gè)列表中。同時(shí)列表中的函數(shù)會(huì)在return語句執(zhí)行后被調(diào)用挑豌。defer常常會(huì)被用來簡(jiǎn)化資源清理釋放之類的操作安券。

舉個(gè)例子,我們來觀察下下面這個(gè)函數(shù)氓英,它的主要功能是打開兩個(gè)文件并將一個(gè)文件的內(nèi)容拷貝到另一個(gè)文件:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

該函數(shù)是可用的侯勉,但是這里有一個(gè)bug。假設(shè)我們?cè)谡{(diào)用os.Create時(shí)出現(xiàn)了失敗的情況铝阐,那么該函數(shù)將會(huì)在沒有關(guān)閉源文件的情況下立即返回址貌。此問題可以很容易地通過在第二個(gè)return語句前調(diào)用src.Close來補(bǔ)救。但如果函數(shù)的功能特別復(fù)雜,該問題就可能不是那么容易被發(fā)現(xiàn)和解決了练对。下面介紹一下defer遍蟋,通過它,我們將可以確保文件總是能被正常關(guān)閉螟凭。

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Defer語句讓我們?cè)诖蜷_文件時(shí)便要思考文件的關(guān)閉虚青,不必在意過多return語句,便可實(shí)現(xiàn)資源的正確釋放螺男。

Defer語句的行為是明確可知的棒厘,此處有三條簡(jiǎn)單的規(guī)則:

  1. 函數(shù)參數(shù)值由defer語句調(diào)用時(shí)確定

比如下面這個(gè)例子,打印出來的變量i的值即是運(yùn)行到defer語句時(shí)的值下隧。在a函數(shù)執(zhí)行return后奢人,Defer后的函數(shù)調(diào)用,即Println淆院,將會(huì)打印出 "0"何乎。

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}
  1. deferred的函數(shù)將會(huì)在return語句之后按照先進(jìn)后出的次序執(zhí)行,即LIFO土辩。

下面這個(gè)函數(shù)的執(zhí)行結(jié)果是 "3210"

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}
  1. deferred函數(shù)還可以讀取return返回值并改變其值宪赶。

在下面的例子中,deferred函數(shù)中對(duì)返回值進(jìn)行了自增操作脯燃,最終函數(shù)c的最終返回值是2.

func c() (i int) {
    defer func() { i++ }()
    return 1
}

這使我們可以非常方便的修改異常的函數(shù)返回。

Panic

panic是go的內(nèi)置函數(shù)蒙保,它可以終止程序的正常執(zhí)行流程并發(fā)出panic(類似其他語言的exception)辕棚。比如當(dāng)函數(shù)F調(diào)用panic,f的執(zhí)行將被終止邓厕,然后defer的函數(shù)正常執(zhí)行完后返回給調(diào)用者逝嚎。對(duì)調(diào)用者而言,F(xiàn)的表現(xiàn)就像調(diào)用者直接調(diào)用了panic详恼。這個(gè)流程會(huì)棧的調(diào)用次序不斷向上拋出panic补君,直到返回到goroutine棧頂,此時(shí)昧互,程序?qū)?huì)崩潰退出挽铁。panic可以通過直接調(diào)用panic產(chǎn)生。同時(shí)也可能由運(yùn)行時(shí)的錯(cuò)誤所產(chǎn)生敞掘,例如數(shù)組越界訪問叽掘。

Recover

recover是go語言的內(nèi)置函數(shù),它的主要作用是可以從panic的重新奪回goroutine的控制權(quán)玖雁。Recover必須通過defer來運(yùn)行更扁。在正常的執(zhí)行流程中,調(diào)用recover將會(huì)返回nil且沒有什么其他的影響。但是如果當(dāng)前的goroutine產(chǎn)生了panic浓镜,recover將會(huì)捕獲到panic拋出的信息溃列,同時(shí)恢復(fù)其正常的執(zhí)行流程。

下面這個(gè)例子向我們展示了panic膛薛、defer和recover的執(zhí)行流程听隐。

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

函數(shù)g接收參數(shù)i,如果i大于3就會(huì)產(chǎn)生panic相叁,否則調(diào)用g(i+1)遵绰。而函數(shù)f通過defer匿名函數(shù)來執(zhí)行recover并打印出捕獲到的panic信息(如r不等于nil)。在閱讀代碼前增淹,可嘗試打印下程序輸出椿访。

輸出如下:

Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

假如我們移除f中的recover,panic就不會(huì)被恢復(fù)并將到傳送到goroutine棧頂虑润,從而終止程序運(yùn)行成玫。如此輸出如下:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
 
panic PC=0x2a9cd8
[stack trace omitted]

下面我們來看一個(gè)真實(shí)的案例,來自go標(biāo)準(zhǔn)庫(kù)的json包拳喻。它先通過一系列的遞歸函數(shù)解析json數(shù)據(jù)哭当。當(dāng)遇到非法json時(shí),解釋器就會(huì)產(chǎn)生panic冗澈,直到上層調(diào)用從panic中重新recover執(zhí)行流程钦勘,并據(jù)此返回適當(dāng)錯(cuò)誤(具體可以參看decode.go文件中的decodeState的error和unmarshal方法)。

在go的庫(kù)中的常見用法是亚亲,即使在包內(nèi)部使用panic彻采,但外部API仍然需要以清晰的error來返回錯(cuò)誤信息。

下面是defer其他的一些使用場(chǎng)景(除了前面列出的file.close案例)捌归,例如鎖的釋放:

mu.Lock()
defer mu.Unlock()

打印頁(yè)尾:

printHeader()
defer printFooter()
and more.

總的來說肛响,defer為我們提供了一種異常強(qiáng)大的流程控制機(jī)制(不僅僅限于panic、recover場(chǎng)景)惜索。而且通過其他一些特殊要求的結(jié)構(gòu)特笋,它可以模仿許多其他語言中的特性。來試試看吧巾兆!

作者:Andrew Gerrand

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猎物,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子角塑,更是在濱河造成了極大的恐慌霸奕,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吉拳,死亡現(xiàn)場(chǎng)離奇詭異质帅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門煤惩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫉嘀,“玉大人,你說我怎么就攤上這事魄揉〖粑辏” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵洛退,是天一觀的道長(zhǎng)瓣俯。 經(jīng)常有香客問我,道長(zhǎng)兵怯,這世上最難降的妖魔是什么彩匕? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮媒区,結(jié)果婚禮上驼仪,老公的妹妹穿的比我還像新娘。我一直安慰自己袜漩,他們只是感情好绪爸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宙攻,像睡著了一般奠货。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上座掘,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天递惋,我揣著相機(jī)與錄音,去河邊找鬼雹顺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛廊遍,可吹牛的內(nèi)容都是我干的嬉愧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喉前,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼没酣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卵迂,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤裕便,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后见咒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偿衰,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了下翎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缤言。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖视事,靈堂內(nèi)的尸體忽然破棺而出胆萧,到底是詐尸還是另有隱情,我是刑警寧澤俐东,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布跌穗,位于F島的核電站,受9級(jí)特大地震影響虏辫,放射性物質(zhì)發(fā)生泄漏蚌吸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一乒裆、第九天 我趴在偏房一處隱蔽的房頂上張望套利。 院中可真熱鬧,春花似錦鹤耍、人聲如沸肉迫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喊衫。三九已至,卻和暖如春杆怕,著一層夾襖步出監(jiān)牢的瞬間族购,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工陵珍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寝杖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓互纯,卻偏偏與公主長(zhǎng)得像瑟幕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子留潦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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

  • golang中defer,panic,recover是很常用的三個(gè)特性只盹,三者一起使用可以充當(dāng)其他語言中try…ca...
    smoke_zl閱讀 27,700評(píng)論 2 28
  • 1、簡(jiǎn)介 Go具有控制流程的常用機(jī)制:if兔院,for殖卑,switch,goto坊萝。 它還有g(shù)o語句在單獨(dú)的gorouti...
    沈淵閱讀 986評(píng)論 0 2
  • 今天同樣孵稽,給大家分享下施羅特中簡(jiǎn)易的三維矯正運(yùn)動(dòng)许起,這種運(yùn)動(dòng)適合一些弧度介于15°至25°的輕微脊柱側(cè)患者。一...
    南柯一夢(mèng)wf閱讀 524評(píng)論 0 0
  • 世界那么美 哪有空去傷悲 打破吧 所有的枷鎖 放下吧 所有的束縛 這世界也沒那么美 哪有空去傷悲
    我心飛翔lijing閱讀 197評(píng)論 0 1
  • vue去掉#號(hào)需要在 router上加上以下參數(shù): mode: 'history',base:'/項(xiàng)目名稱/目錄...
    張大丶閱讀 1,609評(píng)論 0 0