[golang note] 錯誤處理

錯誤處理
• 錯誤處理的標準模式
   golang錯誤處理的標準模式:error接口。
   golang函數如果要返回錯誤,規範上是將error作爲多返回值中的最後一個,但這並非是強制要求。

▶ error接口

type error interface {
       Error() string
}

▶ 內置的error類型使用

▪ 語法如下

func 函數名(參數列表) (返回值列表, err error) {
    // 函數體
}

▪ 錯誤處理

  例如我們有一個這樣的函數:

func Foo(param int) (n int, err error) {
  // 函數體
}

   調用函數時建議按如下方式處理錯誤:

▪ 示例如下

package main

import (
    "errors"
    "fmt"
)

func divide(dividend float64, divisor float64) (result float64, err error) {
    if divisor == 0 {
        return -1, errors.New("除數爲0")
    }

    return dividend / divisor, nil
}

func main() {
    result, err := divide(1, 2)
    if err != nil {
        fmt.Println(err.Error())
    } else {
        fmt.Println("result =", result)
    }

    result, err = divide(1, 0)
    if err != nil {
        fmt.Println(err.Error())
    } else {
        fmt.Println("result =", result)
    }
}

▶ 自定義error類型使用

  golang錯誤處理支持自定義的error類型,只需要爲自定義error類型實現Error接口即可。

▪ 語法如下

type CustomError struct {
    ...
}

func (e *CustomError) Error() string {
    // 函數體
}

▪ 示例如下

package main

import (
    "fmt"
)

type MathError struct {
    Op   string
    info string
}

func (e *MathError) Error() string {
    return "Math operation " + e.Op + " error : " + e.info
}

func divide(dividend float64, divisor float64) (result float64, err error) {
    if divisor == 0 {
        return -1, &MathError{"division", "divisor is zero"}
    }

    return dividend / divisor, nil
}

func main() {
    result, err := divide(1, 2)
    if err != nil {
        fmt.Println(err.Error())
    } else {
        fmt.Println("result =", result)
    }

    result, err = divide(1, 0)
    if err != nil {
        fmt.Println(err.Error())
    } else {
        fmt.Println("result =", result)
    }
}

▪ 類型轉換

  如果處理錯誤時需要獲取詳細信息,而不僅僅滿足於打印一句錯誤信息,那就需要用到類型轉換。

package main

import (
    "fmt"
)

type MathError struct {
    Op   string
    info string
}

func (e *MathError) Error() string {
    return "Math operation " + e.Op + " error : " + e.info
}

func divide(dividend float64, divisor float64) (result float64, err error) {
    if divisor == 0 {
        return -1, &MathError{"division", "divisor is zero"}
    }

    return dividend / divisor, nil
}

func main() {
    result, err := divide(1, 0)
    if err != nil {
        // error類型轉換爲*MathError指針,因爲接口定義傳入類型對象爲*MathError指針
        // 如果接口定義時傳入類型對象爲MathError,那麼這裏的寫法爲err.(MathError)
        if e, ok := err.(*MathError); ok {
            fmt.Println(e.info)
        }
    } else {
        fmt.Println("result =", result)
    }
}

資源釋放
   在c++程序中,經常要注意內存指針、文件句柄、網絡套接字等等資源的釋放,特別需要注意其釋放的時機。而golang使用defer
關鍵字和背後的內部機制簡單地解決了資源釋放的問題。

   1、 defer關鍵字能保證其後的代碼能在函數退出前調用。
   2、一個函數中可以存在多個defer語句,需要注意的是defer語句的調用是遵照先進後出的原則,即最後一個defer語句將最先被執行。
  3、 可以在defer後加一個匿名函數來進行復雜的清理工作。

• 簡單的清理工作
▶ 語法如下

func 函數名(參數列表) (返回值列表) {
    ...
    // 資源申請
    defer 清理函數
    ...
}

▶ 示例如下

package main

import (
    "io"
    "os"
)

func CopyFile(dst, src string) (w int64, err error) {
    srcFile, err := os.Open(src)
    if err != nil {
        return
    }
    defer srcFile.Close()

    dstFile, err := os.Create(dst)
    if err != nil {
        return
    }
    defer dstFile.Close()

    return io.Copy(dstFile, srcFile)
}

func main() {
    CopyFile("D:/2.txt", "D:/1.txt")
}

▶ 先進後出規則

package main

import (
    "fmt"
)

func Test() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
}

func main() {
    Test()
}

• 複雜的清理工作
▶ 語法如下

func 函數名(參數列表) (返回值列表) {
    ...
    // 資源申請
    defer func() {
        // 複雜的清理工作
    } ()
    ...
}

▶ 示例如下

package main

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

func CopyFile(dst, src string) (w int64, err error) {
    srcFile, err := os.Open(src)
    if err != nil {
        return
    }
    defer func() {
        fmt.Println("close file :", src)
        srcFile.Close()
    }()


    dstFile, err := os.Create(dst)
    if err != nil {
        return
    }
    defer func() {
        fmt.Println("close file :", dst)
        dstFile.Close()
    }()

    return io.Copy(dstFile, srcFile)
}

func main() {
    CopyFile("D:/2.txt", "D:/1.txt")
}

異常處理
  一些高級語言中一般提供類似try…catch…finally…的語法,用於捕獲異常。golang提供panic和recover兩個關鍵字用於異常處理。

• panic
panic在golang中是一個內置函數,接收一個interface{}類型的值作爲參數:

func panic(interface{}) {
    ...
}

  當一個函數執行過程中調用panic函數時,函數執行流程將立即終止,但panic之前的defer關鍵字延遲執行的語句將正常執行,之後該函數將返回到上層調用函數,並逐層向上執行panic流程,直至函數所屬的goroutine中所有正在執行函數終止。錯誤信息將被報告,包括在調用panic()函數時傳入的參數。下面用一個示例說明:

package main

import (
    "fmt"
)

func MyFunc1() {
    defer fmt.Println("MyFunc1 defer 1")

    panic("MyFunc1 panic test")

    defer fmt.Println("MyFunc1 defer 2")
}

func MyFunc2() {
    defer fmt.Println("MyFunc2 defer 1")

    MyFunc1()

    defer fmt.Println("MyFunc2 defer 2")
}

func main() {
    MyFunc2()
}

程序輸出如下:
      panic前的defer先輸出了
這裏寫圖片描述

• recover
  recover在golang中是一個內置函數,返回一個interface{}類型的值作爲參數:

func recover() interface{} {
    ...
}

  panic函數觸發後不會立即返回,而是先defer,再返回。如果defer的時候,有辦法將panic捕獲到,然後及時進行異常處理,並阻止panic傳遞,那處理機制就完善了。因此golang提供了recover內置函數,用於捕獲panic並阻止其向上傳遞。需要注意的是,recover之後,邏輯並不會恢復到panic處,函數還是會在defer之後返回,但是所屬goroutine將不會退出。

▶ 本層函數處理

package main

import (
    "fmt"
)

func MyFunc1() {
    defer func() {
        fmt.Println("MyFunc1 defer 1")
        if r := recover(); r != nil {
            fmt.Println("Runtime error caught :", r)
        }
    }()

    panic("MyFunc1 panic test")

    fmt.Println("MyFunc1 defer 2")
}

func MyFunc2() {
    defer fmt.Println("MyFunc2 defer 1")

    MyFunc1()

    defer fmt.Println("MyFunc2 defer 2")
}

func main() {
    MyFunc2()
}

程序輸出如下:
panic輸出,在recover撲捉painc後的defer沒輸出
這裏寫圖片描述

▶ 上層函數處理

package main

import (
    "fmt"
)

func MyFunc1() {
    defer fmt.Println("MyFunc1 defer 1")

    panic("MyFunc1 panic test")

    fmt.Println("MyFunc1 defer 2")
}

func MyFunc2() {
    defer func() {
        fmt.Println("MyFunc1 defer 2")
        if r := recover(); r != nil {
            fmt.Println("Runtime error caught :", r)
        }
    }()

    MyFunc1()

    defer fmt.Println("MyFunc2 defer 2")
}

func main() {
    MyFunc2()
}

程序輸出:
     recover撲捉到painc後的defer不會輸出
這裏寫圖片描述

• 模擬try…catch…語法
▶ 語法如下

func Try(f func(), handler func(interface{})) {
    defer func() {
        if err := recover(); err != nil {
            handler(err)
        }
    }()

    f()
}

▶ 示例如下

package main

import (
    "fmt"
)

func Try(f func(), handler func(interface{})) {
    defer func() {
        if err := recover(); err != nil {
            handler(err)
        }
    }()

    f()
}

func main() {
    Try(func() {
        panic("main panic")
    }, func(e interface{}) {
        fmt.Println(e)
    })
}

來源:http://www.cnblogs.com/heartchord/p/5236091.html

發佈了32 篇原創文章 · 獲贊 13 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章