go的錯誤處理

wh7577發表於2021-09-09

一 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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章