go的錯誤處理
一 error介面
GO語言中的error型別實際上是抽象Error()方法的error介面
type error interface{
Error() string
}
GO語言使用該介面進行標準的錯誤處理。
對於大多數函式,如果要返回錯誤,大致上都可以定義為如下模式,將error作為多種返回值中的最後一個,但這並非是強制要求:
func Foo (param int) (n int,err error){
// .....
}
呼叫時的程式碼建議按照如下方式處理錯誤情況
n,err := Foo(0)
if err != nil {
//錯誤處理
}else{
//使用返回值n
}
看下面的例子綜合了一下error介面的用法:
package mainimport( "fmt" )//自定義錯誤型別type ArithmeticError struct { error //實現error介面}//重寫Error()方法func (this *ArithmeticError) Error() string { return "自定義的error,error名稱為算數不合法" } //定義除法運算函式func Devide(num1, num2 int) (rs int, err error) { if num2 == 0 { return 0, &ArithmeticError{} } else { return num1 / num2, nil }}func main() { var a, b int fmt.Scanf("%d %d", &a, &b) rs, err := Devide(a, b) if err != nil { fmt.Println(err) } else { fmt.Println("結果是:", rs) }}
執行,輸入引數5 2(正確的情況):
5 2結果是: 2
若輸入5 0(產生錯誤的情況):
5 0自定義的error,error名稱為算數不合法
透過上面的例子可以看出error型別類似於Java中的Exception型別,不同的是Exception必須搭配throw和catch使用。
二、defer--延遲語句
在GO語言中,可以使用關鍵字defer向函式註冊退出呼叫,即主函式退出時,defer後的函式才會被呼叫。
defer語句的作用是不管程式是否出現異常,均在函式退出時自動執行相關程式碼。(相當於Java中的finally)
當函式執行到最後時,這些defer語句會按照逆序執行,最後該函式返回。
例如:
package mainimport ( "fmt")func main() { for i := 0; i < 5; i++ { defer fmt.Println(i) }}
其執行結果為:
43210
defer語句在宣告時被載入到記憶體(多個defer語句按照FIFO原則) ,載入時記錄變數的值,而在函式返回之後執行,看下面的例子:
例子1:defer語句載入時記錄值
func f1() { i := 0 defer fmt.Println(i) //實際上是將fmt.Println(0)載入到記憶體 i++ return}func main() { f1()}
其結果顯然是0
例子2:在函式返回後執行
func f2() (i int) { var a int = 1 defer func() { a++ fmt.Println("defer內部", a) }() return a}func main() { fmt.Println("main中", f2())}
其結果是
defer內部 2main中 1
例子3:defer語句會讀取主調函式的返回值,並對返回值賦值.(注意和例子2的區別)
func f3() (i int) { defer func() { i++ }() return 1}func main() { fmt.Println(f3())}
其結果竟然是2.
透過上面的幾個例子,自然而然會想到用defer語句做清理工作,釋放記憶體資源(這樣你再也不會為Java中的try-catch-finally層層巢狀而苦惱了)
例如關閉檔案控制程式碼:
srcFile,err := os.Open("myFile")defer srcFile.Close()
關閉互斥鎖:
mutex.Lock()defer mutex.Unlock()
上面例子中defer語句的用法有兩個優點:
1.讓設計者永遠也不會忘記關閉檔案,有時當函式返回時常常忘記釋放開啟的資源變數。
2.將關閉和開啟靠在一起,程式的意圖變得清晰很多。
下面看一個檔案複製的例子:
package mainimport ( "fmt" "io" "os" )func main() { copylen, err := copyFile("dst.txt", "src.txt") if err != nil { return } else { fmt.Println(copylen) }}//函式copyFile的功能是將原始檔sec的資料複製給dstfunc copyFile(dstName, srcName string) (copylen int64, err error) { src, err := os.Open(srcName) if err != nil { return }//當return時就會呼叫src.Close()把原始檔關閉 defer src.Close() dst, err := os.Create(dstName) if err != nil { return } //當return是就會呼叫src.Close()把目標檔案關閉 defer dst.Close() return io.Copy(dst, src)}
可以看到確實比Java簡潔許多。
三panic-recover執行時異常處理機制
Go語言中沒有Java中那種try-catch-finally結構化異常處理機制,而使用panic()函式答題throw/raise引發錯誤,
然後在defer語句中呼叫recover()函式捕獲錯誤,這就是Go語言的異常恢復機制——panic-recover機制
兩個函式的原型為:
func panic(interface{})//接受任意型別引數 無返回值func recover() interface{}//可以返回任意型別 無引數
一定要記住,你應當把它作為最後的手段來使用,也就是說,你的程式碼中應當沒有,或者很少有panic的東西。這是個強大的工具,請明智地使用
它。那麼,我們應該如何使用它呢?
panic()
是一個內建函式,可以中斷原有的控制流程,進入一個令人panic(恐慌即Java中的異常)的流程中。當函式F呼叫panic,函式F的執行被中
斷,但是F中的延遲函式(必須是在panic之前的已載入的defer)會正常執行,然後F返回到呼叫它的地方。在呼叫的地方,F的行為就像呼叫了panic。這一
過程繼續向上,直到發生panic的goroutine中所有呼叫的函式返回,此時程式退出。異常可以直接呼叫panic產
生。也可以由執行時錯誤產生,例如訪問越界的陣列。
recover()
是一個內建的函式,可以讓進入令人恐慌的流程中的goroutine恢復過來。recover僅在延遲函式中有效。在正常
的執行過程中,呼叫recover會返回nil,並且沒有其它任何效果。如果當前的goroutine陷入panic,呼叫
recover可以捕獲到panic的輸入值,並且恢復正常的執行。
一般情況下,recover()應該在一個使用defer關鍵字的函式中執行以有效擷取錯誤處理流程。如果沒有在發生異常的goroutine中明確呼叫恢復
過程(使用recover關鍵字),會導致該goroutine所屬的程式列印異常資訊後直接退出。
這裡結合自定義的error型別給出一個使用panic和recover的完整例子:
package mainimport ( "fmt")//自定義錯誤型別type ArithmeticError struct { error}//重寫Error()方法func (this *ArithmeticError) Error() string { return "自定義的error,error名稱為算數不合法" }//定義除法運算函式***這裡與本文中第一幕①error介面的例子不同func Devide(num1, num2 int) int { if num2 == 0 { panic(&ArithmeticError{}) //當然也可以使用ArithmeticError{}同時recover等到ArithmeticError型別 } else { return num1 / num2 }}func main() { var a, b int fmt.Scanf("%d %d", &a, &b) defer func() { if r := recover(); r != nil { fmt.Printf("panic的內容%vn", r) if _, ok := r.(error); ok { fmt.Println("panic--recover()得到的是error型別") } if _, ok := r.(*ArithmeticError); ok { fmt.Println("panic--recover()得到的是ArithmeticError型別") } if _, ok := r.(string); ok { fmt.Println("panic--recover()得到的是string型別") } } }() rs := Devide(a, b) fmt.Println("結果是:", rs)}
其執行的結果為:
使用與上面相同的測試資料,輸入5 2得:
5 2結果是: 2
輸入5 0得:
5 0panic的內容自定義的error,error名稱為算數不合法panic--recover()得到的是error型別panic--recover()得到的是ArithmeticError型別
可見已將error示例程式轉換為了Java中的用法,但是在大多數程式中使用error處理的方法較多。
需要注意的是:defer語句定義的位置 如果defer放在了
rs := Devide(a, b)語句之後,defer將沒有機會執行即:
rs := Devide(a, b) defer func() { if r := recover(); r != nil { fmt.Printf("panic的內容%vn", r) if _, ok := r.(error); ok { fmt.Println("panic--recover()得到的是error型別") } if _, ok := r.(*ArithmeticError); ok { fmt.Println("panic--recover()得到的是ArithmeticError型別") } if _, ok := r.(string); ok { fmt.Println("panic--recover()得到的是string型別") } } }()
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2480/viewspace-2818276/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Go 錯誤處理Go
- Go語言之錯誤處理Go
- Go 的錯誤處理策略 筆記Go筆記
- go 錯誤處理設計思考Go
- Go 語言的錯誤訊息處理Go
- [譯] Go 1.13 errors 包錯誤處理GoError
- 開心檔之Go 錯誤處理Go
- Go 併發 2.2:錯誤處理模式Go模式
- Go語言基礎-錯誤處理Go
- Go語言錯誤處理機制Go
- 如何在 Go 中優雅的處理和返回錯誤(1)——函式內部的錯誤處理Go函式
- 二、GO 程式設計模式:錯誤處理Go程式設計設計模式
- 寫給go開發者的gRPC教程-錯誤處理GoRPC
- 錯誤處理
- 兄弟連go教程(15)函式 - 錯誤處理Go函式
- 深入 Go 的錯誤處理機制,理解設計思想Go
- 深入 Go 的錯誤處理機制,學會如何使用Go
- 優化 Go 錯誤處理,魔改 Go recover 靠譜嗎?優化Go
- Go語言(golang)的錯誤(error)處理的推薦方案GolangError
- Go 為什麼不像 Rust 用 ?!做錯誤處理?GoRust
- 清華尹成帶你實戰GO案例(24)Go 錯誤處理Go
- PHP 錯誤處理PHP
- php錯誤處理PHP
- Swift錯誤處理Swift
- Zabbix錯誤處理
- mysqldump錯誤處理MySql
- Go多協程併發環境下的錯誤處理Go
- axios 的錯誤處理iOS
- COM的錯誤處理 (轉)
- 【GoLang 那點事】深入 Go 的錯誤處理機制 (一) 使用Golang
- 錯誤處理:如何通過 error、deferred、panic 等處理錯誤?Error
- 從錯誤處理看 Rust 的語言和 Go 語言的設計RustGo
- PHP錯誤處理和異常處理PHP
- Python錯誤處理Python
- 請教 Element 的錯誤處理
- Restful API 中的錯誤處理RESTAPI
- 【譯】RxJava 中的錯誤處理RxJava
- grpc中的錯誤處理RPC