錯誤和異常的區(qū)別
- 錯誤和異常是不同的概念
- 錯誤指的是可能出現(xiàn)問題的地方出現(xiàn)了問題吆你,比如打開一個文件時失敗,這種情況在人們的意料之中 左冬;而異常指的是不應該出現(xiàn)問題的地方出現(xiàn)了問題筐摘,比如引用了空指針鸦泳,這種情況在人們的意料之外∷信危可見惠豺,錯誤是業(yè)務過程的一部分,而異常不是
- 從Go語言機制上講风宁,錯誤和異常就是error和panic的區(qū)別
- Go語言錯誤和異常是可以互相轉換的:
- 錯誤轉異常洁墙,如程序嘗試請求某個URL,最多嘗試三次戒财,嘗試三次的過程中請求失敗是錯誤热监,嘗試完第三次還不成功的話,失敗就被提升為異常了
- 異常轉錯誤饮寞,如panic觸發(fā)的異常被recover恢復后孝扛,將返回值中error類型的變量進行賦值列吼,以便上層函數(shù)繼續(xù)走錯誤處理流程
Go語言錯誤處理機制
error
- Go 語言通過內(nèi)置的錯誤接口提供了非常簡單的錯誤處理機制
type error interface {
Error() string
}
- Go提供了兩種創(chuàng)建一個實現(xiàn)了error interface的類型的變量實例的方法:errors.New和fmt.Errorf
errors.New("your first error code")
fmt.Errorf("error value is %d\n", errcode)
- 如果函數(shù)要返回錯誤,則在返回值類型列表中包含error苦始,使用errors.New 可返回一個錯誤信息
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
}
//調(diào)用函數(shù)
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
錯誤處理的正確姿勢
- 失敗的原因只有一個寞钥,所以返回值的類型應該為bool,而不是error
- 沒有失敗時陌选,不使用error
- error應放在返回值類型列表的最后
- 錯誤值統(tǒng)一定義理郑,而不是跟著感覺走
- 參考C/C++的錯誤碼定義文件,在Go的每個包中增加一個錯誤對象定義文件咨油,如下所示:
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")
- 錯誤逐層傳遞時您炉,層層都加日志
- 當嘗試幾次可以避免失敗時,不要立即返回錯誤
- 當上層函數(shù)不關心錯誤時役电,建議不返回error赚爵,打印日志即可
- 當發(fā)生錯誤時,不忽略有用的返回值
- error的string context中的內(nèi)容格式:頭母小寫法瑟,結尾不帶標點
- 不要忽略函數(shù)或方法返回的錯誤碼冀膝,Check it
- 可在defer中處理error
Go語言異常處理機制
- Go語言追求簡潔優(yōu)雅,所以瓢谢,Go語言不支持傳統(tǒng)的 try…catch…finally 這種異常畸写,因為Go語言的設計者們認為,將異常與控制結構混在一起會很容易使得代碼變得混亂氓扛。導致開發(fā)者很容易濫用異常,甚至一個小小的錯誤都拋出一個異常论笔。不要用異常代替錯誤采郎,更不要用來控制流程
- 在極個別的情況下,也就是說狂魔,遇到真正的異常的情況下(比如除數(shù)為0了)蒜埋。才使用Go中引入的異常處理函數(shù)
- Go中引入兩個內(nèi)置函數(shù)panic和recover來觸發(fā)和終止異常處理流程,同時引入關鍵字defer來延遲執(zhí)行defer后面的函數(shù)最楷,用于處理異常
- panic和recover函數(shù)在作用層面分別對等throw和catch語句整份,當然也存在不同之處,panic和recover函數(shù)適用于那些真正的異常(例如整數(shù)除0)籽孙,而throw catch finally機制常常被用來處理一些業(yè)務層面的自定義異常和錯誤
- go語言中烈评,panic和recover要慎用
panic
- Go使用panic函數(shù)觸發(fā)異常,它可以在任何地方引發(fā)
- panic一般會導致程序掛掉(除非recover) 然后Go運行時會打印出調(diào)用棧
- 即使函數(shù)執(zhí)行的時候panic了犯建,函數(shù)不往下走了讲冠,運行時并不是立刻向上傳遞panic,而是到defer那适瓦,等defer的東西都跑完了竿开,panic再向上傳遞
func abc() {
panic("我是abc,我要拋出一個異常了谱仪,等下defer會通過recover捕獲這個異常,然后正常處理否彩,使后續(xù)程序正常運行疯攒。")
fmt.Println("我是PHP里panic后面要打印出的內(nèi)容。")
}
recover&defer
- Go使用defer和recover處理和恢復異常
- panic會在調(diào)用它的函數(shù)中向本層和它的所有上層逐級拋出列荔,若一直沒有recover將其捕獲敬尺,程序退出后會產(chǎn)生crash;若在某層defer語句中被recover捕獲肌毅,控制流程將進入到recover之后的語句中
- 必須要先聲明defer筷转,否則不能捕獲到panic異常,也就是說要先注冊函數(shù),后面有異常了悬而,才可以調(diào)用
- defer 有點類似 try-catch-finally 中的 finally
- 可以在一個函數(shù)中執(zhí)行多條defer語句呜舒,它們的執(zhí)行順序與聲明順序相反(堆棧,先進后出)
- recover只有在defer調(diào)用的函數(shù)中有效
- 選擇在一個合適的上游去recover笨奠,并打印堆棧信息袭蝗,使得部署后的程序不會終止
func abc() {
defer func() {
if err := recover(); err != nil {
fmt.Println("終于捕獲到了panic產(chǎn)生的異常:", err) // 這里的err就是panic傳入的內(nèi)容
fmt.Println("我是defer里的匿名函數(shù),我捕獲到panic的異常了般婆,我要recover恢復過來 了到腥。")
}
}() //注意這個()就是調(diào)用該匿名函數(shù)的
panic("我是abc,我要拋出一個異常了,等下defer會通過recover捕獲這個異常蔚袍,捕獲到我時乡范,在abc里是不會輸出的,會在defer里被捕獲輸出啤咽,然后正常處理晋辆,使后續(xù)程序正常運行")
fmt.Println("我是panic后面要打印出的內(nèi)容。但是我是永遠也打印不出來了宇整。因為邏輯并不會恢復到panic那個點去瓶佳,函數(shù)還是會在defer之后返回,也就是說執(zhí)行到defer后鳞青,程序直接返回到main()里霸饲,接下來開始執(zhí)行cba()")
}
func cba() {
fmt.Println("我是cba,如果沒有defer來recover捕獲panic的異常臂拓,我是不會被正常執(zhí)行的厚脉。")
}
異常處理的正確姿勢
- 在程序開發(fā)階段,調(diào)用panic函數(shù)來中斷程序的執(zhí)行以強制發(fā)生異常埃儿,使得該異常不會被忽略器仗,因而能夠盡快被修復
- 在程序部署后,應恢復異常避免程序終止
- 在調(diào)用recover的延遲函數(shù)中以最合理的方式響應該異常:
打印堆棧的異常調(diào)用信息和關鍵的業(yè)務信息,以便這些問題保留可見
將異常轉換為錯誤精钮,以便調(diào)用者讓程序恢復到健康狀態(tài)并繼續(xù)安全運行
- 對于不應該出現(xiàn)的分支威鹿,使用異常處理
- 針對入?yún)⒉粦撚袉栴}的函數(shù),使用panic設計
- 異常處理的作用域(場景):
空指針引用
下標越界
除數(shù)為0
不應該出現(xiàn)的分支轨香,比如default
輸入不應該引起函數(shù)錯誤