【Go語言】錯誤與異常處理機制

①error接口數組

Go語言中的error類型其實是抽象了Error()方法的error接口ide

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接口的用法:this

package main

import (
    "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(正確的狀況):spa

5 2
結果是: 2

 

若輸入5 0(產生錯誤的狀況):設計

5 0
自定義的error,error名稱爲算數不合法

 

經過上面的例子能夠看出error類型相似於Java中的Exception類型,不一樣的是Exception必須搭配throw和catch使用。code

②defer--延遲語句blog

在Go語言中,可使用關鍵字defer向函數註冊退出調用,即主調函數退出時,defer後的函數纔會被調用。
defer語句的做用是無論程序是否出現異常,均在函數退出時自動執行相關代碼。(至關於Java中的finally )

當函數執行到最後時,這些defer語句會按照逆序執行,最後該函數返回。

例如:

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
}

 

其執行結果爲:

4
3
2
1
0

 

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內部 2
main中 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 main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    copylen, err := copyFile("dst.txt", "src.txt")
    if err != nil {
        return
    } else {
        fmt.Println(copylen)
    }

}

//函數copyFile的功能是將源文件sec的數據複製給dst
func 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 main

import (
    "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的內容%v\n", 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 0
panic的內容自定義的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的內容%v\n", 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類型")
            }
        }
    }()

 

由於在在陷入panic以前defer語句沒有被加載到內存,而在執行panic時程序被中斷,於是沒法執行defer語句。
相關文章
相關標籤/搜索