Go的異常處理機制

在實際的項目中,對於異常的最佳實踐不少,在使用不一樣的語言開發不一樣類型的程序時有不一樣的建議。Go語言中沒有使用try...catch相似的異常處理機制,而是提供了panicrecover函數來處理所謂的運行時異常,也就是所謂的錯誤處理機制。配合defer語句和error接口開發者能夠很是靈活地處理運行時的錯誤和異常。bash

代碼中隨便使用panic顯然不是一個好方式,咱們知道在風險控制中,有所謂已知的未知未知的未知,在Go程序設計中,對於前者咱們能夠經過預先開發的代碼分支來處理,對於後者就比較棘手了:ide

  1. 若是項目中的代碼、使用的標準庫以及第三方庫在運行時內部捕獲了異常並經過合適的error對象返回給調用者,那咱們能夠儘可能少甚至能夠不用panic函數。
  2. 若是沒法保證上面的狀況,那爲了確保程序在運行時不會由於未知的未知致使崩潰,那panic函數可能不得不加在任何須要的地方。

咱們應該認識到,panic函數是咱們和計算機都不但願看到的,應該在設計開發的時候充分考慮使用場景可能出現的狀況,處理好已知的未知,在肯定須要的地方使用panic機制。函數

defer

defer關鍵字用來標記最後執行的Go語句,通常用在資源釋放、關閉鏈接等操做,會在函數關閉前調用。多個defer的定義與執行相似於棧的操做:先進後出,最早定義的最後執行。ui

package main

import (
    "fmt"
)

func testA() int {
    x := 5
    defer func() {
    	x++
    	fmt.Printf("testA defer func-1[x=>%d]\n", x)
    }()
    defer func() {
    	x++
    	fmt.Printf("testA defer funn-2[x=>%d]\n", x)
    }()
    return x
}

func testB() (x int) {
    x = 5
    defer func() {
    	fmt.Println("testB defer func")
    }()
    x++
    return
}

func main() {
    a := testA()
    b := testB()
    fmt.Printf("testA return %d\n", a)
    fmt.Printf("testB return %d\n", b)
}
複製代碼

執行後輸出結果:編碼

testA defer funn-2[x=>6]
testA defer func-1[x=>7]
testB defer func
testA return 5
testB return 6
複製代碼

分析如上代碼,return xxx語句並非一條原子指令,在其執行的時候語句會分解成返回變量=xxxreturn,最後執行return。以前說defer語句是在函數關閉的時候調用,確切的說是在執行return語句的時候調用,注意是return不是return xxx。在同一個函數裏的defer會按照棧的先進後出原則執行。spa

Error

Go語言經過支持多返回值,讓在運行時返回詳細的錯誤信息給調用者變得很是方便。咱們能夠在編碼中經過實現error接口類型來生成錯誤信息,error接口的定義以下:設計

type error interface {
    Error() string
}
複製代碼

經過下面的例子來看一下error接口的使用:code

package main

import (
    "fmt"
)

type error interface {
    Error() string
}

// DivisionError 類型定義
type DivisionError struct {
    dividend int
    divider  int
}

// Error 方法實現
func (de *DivisionError) Error() string {
    strFormat := ` Cannot proceed, the divider is zero. dividend: %d divider: 0`
    return fmt.Sprintf(strFormat, de.dividend)
}

// Divide 函數定義
func Divide(varDividend int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
    	dData := DivisionError{
            dividend: varDividend,
            divider:  varDivider,
    	}
    	errorMsg = dData.Error()
    } else {
    	result = varDividend / varDivider
    }
    return
}

func main() {
    result, err := Divide(100, 10)
    if err == "" {
    	fmt.Printf("100/10=%d\n", result)
    }
    _, err = Divide(100, 0)
    if err != "" {
    	fmt.Printf("errorMsg is: %s\n", err)
    }
}
複製代碼

運行後能夠看到以下輸出:orm

100/10=10
errorMsg is:
        Cannot proceed, the divider is zero.
        dividend: 100
        divider: 0
複製代碼

panic和recover

panic和recover是兩個內置函數,panic函數用於拋出異常,recover函數用於捕獲panic函數的參數信息。recover函數只有在defer語句調用的函數中直接調用時才能生效,若是goroutine沒有panic,那recover函數會返回nil。對象

package main

import (
    "fmt"
)

// SimplePanicRecover panic/recover簡單的例子
func SimplePanicRecover() {
    defer func() {
    	if err := recover(); err != nil {
    	    fmt.Println("Panic info is:", err)
    	}
    }()
    panic("SimplePanicRecover panic!")
}

// MultiPanicRecover 多個panic/recover
// 當defer中也調用了panic函數時,最後被調用的panic函數的參數會被後面的recover函數獲取到
// 一個函數中能夠定義多個defer函數,按照FILO的規則執行
func MultiPanicRecover() {
    defer func() {
    	if err := recover(); err != nil {
    	    fmt.Println("Panic info is:", err)
    	}
    }()
    defer func() {
    	panic("MultiPanicRecover defer inner panic!")
    }()
    defer func() {
    	if err := recover(); err != nil {
    	    fmt.Println("Panic info is:", err)
    	}
    }()
    panic("MultiPanicRecover panic!")
}

// NoPanicButHasRecover 只有panic沒有recover
// 若是函數沒有panic函數,調用recover函數不會獲取到任何信息,也不會影響當前進程
func NoPanicButHasRecover() {
    if err := recover(); err != nil {
    	fmt.Println("NoPanicButHasRecover Panic info is:", err)
    } else {
    	fmt.Println("NoPanicButHasRecover Panic info is:", err)
    }
}

func main() {
    SimplePanicRecover()
    MultiPanicRecover()
    NoPanicButHasRecover()
}
複製代碼

運行後能夠看到以下輸出:

Panic info is: SimplePanicRecover panic!
Panic info is: MultiPanicRecover panic!
Panic info is: MultiPanicRecover defer inner panic!
NoPanicButHasRecover Panic info is: <nil>
複製代碼
相關文章
相關標籤/搜索