前言
本篇想記錄一下,剛開始學習golang寫API server的時候想要寫error log時,所遇到的問題
底下是拜了google大神之後, 大部分寫CRUD的起手式
1 | package main |
panic 以及 fatal 這兩個log function便引起我的好奇心
字面意思上看起來都是很嚴重的錯誤,執行後也都立即結束程式
那到底什麼時候該用哪個才是比較符合go的convention呢?
所以本篇想要試著整理出底下幾個問題的答案
- 兩者的差異性?
- 使用的時機點?
- Go建議的做法?
差異性
為了進一步兩者的差別,我先從godoc的原始碼著手
1 |
|
原來背後是os.Exit(1) v.s panic(s)
那這兩者在執行上又有什麼差異呢?
Panic & Recover
在查閱godoc有關於panic的說明註解之後
它其實是搭配另外一個builtin function recover一起使用的
類似C++/Java/Python,try catch block的模式
具體而言,當panic發生時,go runtime會執行幾件事
- Unwind program stack until the top-level funciton or the panic is recovered
- Execute deferred functions along the stack(LIFO)
- If not recovered, print panic goroutine stacks and exit program with status 2 (syscall)
白話來說就是
- 當panic發生時,發生panic的function會停在呼叫panic的地方
- 從panic的function開始依序往caller端(此goroutine)執行每個function中已紀錄的deferred functions, 因為是stack所以會是後進先出的順序,直到呼叫頂端(main或goroutine的起始function)
- 如果過程中都沒有遇到recover,則印出error的訊息(傳進panic的參數)及印出發生panic的stack trace
- 回傳錯誤碼2
這整個執行流程就叫做panicking
而recover就是用來調控panicking的流程,類似catch,當panic發生後的錯誤處理,讓goroutine能夠繼續執行
用法如下
1 | func PanicF() { |
特別留意recover必須放在defer的function裡才能夠啟到回復的作用
os.Exit(1)
而相對於panic結束前需要的繁複手續
os.Exit(1)就顯得比較直接乾脆了結
1 | // Exit causes the current program to exit with the given status code. |
基本上當錯誤發生時,就直接呼叫os level的system call結束process並回傳傳入的錯誤碼1
使用時機
以下是個人的觀點及整理收集的資訊
這個問題可以被想成os.Exit()及panic()的在例外或錯誤處理時的適用時機
panic()
-
An unrecoverable error that the program should stop execution
今天如果error是程式無法自行復原(e.g., 帳密打錯),程式需要馬上終止執行,這時候就可以用panic()(log.Panic())來報錯,因為它會執行這個發生panic goroutine的deferred functions(clean up),能做到程序的正常結束(graceful shutdown)
-
A programmer error
另外一種Use case可以從go的原始碼看到,錯誤地方式呼叫function(e.g., 穿入參數不符合規範),這也同時符合panic結束的error status 是2的設計
-
Deep nested errors
還有一種是良葛格及wiki裡有提到,如果錯誤發生在比較深的call stacks裡 (e.g., package internal private function),這時候就可以考慮用panic(err)直接往最外層(Exposed function)拋,再利用recover來轉譯出error,但注意在go的convention裡,panic state 不應該跨package,package之間還是要盡量要以error來溝通
os.Exit()
查看golang的原始碼
主要可以歸類成兩種類型
- top level function (e.g., main)
- test function
直覺上可以理解為不需要做額外的clean up就直接離開,或者測試有錯就馬上停止
雖然log.Fatal()最後呼叫的是os.Exit(1),損失了回傳error code的彈性
但我想在測試的環境中還是蠻適合使用有效率立即停止的error out
Go的建議做法
其實在網路上並沒有針對log.Panic()及log.Fatal()的比較
godoc跟wiki也沒有找到特別針對這部份做說明
但這邊還是記錄一下幾個比較相關的資訊,留著存參
基本上golang是鼓勵好好思考如何處裡錯誤狀況
而不是遇到就直接丟例外,讓程式直接crash
FAQ裡面也有提到golang沒有exceptions或assertions的機制也是為了這個緣故
不過針對os.Exit跟panic
stackoverflow的這個回答蠻精闢的
So basically panic is for you, a bad exit code is for your user.
可歸咎開發者的包就用panic
要給程式使用者的錯誤資訊就用os.Exit
總結
底下是自己的結論
-
log.Panic適用
- 無法處理的錯誤,須立即停止程式
- 提醒function使用的方式錯誤(e.g., 不合法的input提醒)
- 太過深層的private function錯誤
-
log.Fatal適用
- 測試,有問題馬上修
- 不需要clean up的main function錯誤
Reference
- Defer, Panic, and Recover
- PanicAndRecover
- when to use os exit and panic
- panic and recover
- go defer and os exit
- Exitcodes
- DeferPanicRecover