go語言之拋出異常


一: panic和recover 

作用:panic 用來主動拋出錯誤; recover 用來捕獲 panic 拋出的錯誤。

概述: 1,引發panic有兩種情況 1)程序主動調用panic函數 2)程序產生運行時錯誤,由運行時檢測並拋出 過程: ! 發生 panic 后,程序會從調用 panic的函數位置或發生panic 的地方立即返回,逐層向上執行函數的defer語句, 然后逐層打印函數調用堆棧,直到被 recover 捕獲或運行到最外層函數而退出。 ! panic的參數是一個空接口類型 interface{},所以任意類型的變量都可以傳遞給 panic(xxx) ! panic 不但可以在函數正常流程中拋出,在 defer 邏輯里也可以再次調用 panic 或拋出 panic a defer 里面的 panic 能夠被后續執行的 defer 捕獲。 recover()用來捕獲 panic,阻止panic繼續向上傳遞recover()和defer一起使用 ,但是recover() 只  有在defer后面的函數體內被直接調用才能捕獲panic終止異常否則返回 nil,異常繼續向外傳遞

//這會獲取失敗
defer recover()
defer fmt.println(recover())
//嵌套兩層也會獲取失敗
defer func() {
	func(){
		println("defer inner")
		recover() //無效
	}()
}

  以下場景會獲取成功

package main
func f(){
    defer func() {
        println("defer inner")
        recover()
    }()
}
func except()  {
    recover()
}
func test(){
    defer except()
    panic("test panic")
}
func main()  {
    f()
    except()
    test()
}

  可以有多個panic被拋出,連續多個panic的場景只能出現在延遲調用里面,否則不會出現多個panic被拋出的場景。但只有最后一次panic能被捕獲

package main

import "fmt"

func main(){
    defer func() {
        if err :=recover();err !=nil{
            fmt.Println(err)
        }
    }()
  //只有最后一次panic調用被捕獲
    defer func() {
        panic("first defer panic")   //打印結構是這個
    }()
    defer func() {
        panic("second defer panic")
    }()
    panic("main body panic")
}

  包中 in it 函數引發的 panic 只能在 in it 函數中捕獲,在 main 中無法被捕獲,原因是 in it
數先於 main 執行,函數並不能捕獲內部新啟動的 goroutine 所拋出的 panic 。

package main

import (
    "fmt"
    "time"
)

func do()  {
    //這里並不能獲取da函數中的panic
    defer func() {
        if err := recover();err != nil{
            fmt.Println(err)
        }
    }()
    go da()
    go db()
    time.Sleep(3*time.Second)
}
func da(){
    panic("panic da")
    for i := 0; i<10; i++{
        fmt.Println(i)
    }
}
func db(){
    for i := 0; i<10; i++{
        fmt.Println(i)
    }
}
func main()  {
    fmt.Println(do)
    fmt.Println(da)
    fmt.Println(db)
}

  示例

package main

import "fmt"

//系統拋異常
func test01() {
    a := [5]int{0, 1, 2, 3, 4}
    //a[10] = 123
    index := 10
    a[index] = 123
}
func main() {
    //panic: runtime error: index out of range
    //test01()
    //test02()
    //test03()
    test04()
}
//自己拋異常
func test02() {
    getCircleArea(-5)
}
func getCircleArea(radius float32) (area float32) {
    if radius < 0 {
        //拋異常
        panic("半徑不能為負數")
    }
    return 3.14 * radius * radius
}
func test03() {
    //延時執行匿名函數
    //延時到什么時候?要么正常結束,要么出異常
    //recover()是復活的意思
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    //會報錯
    getCircleArea(-5)
    //下句話沒有打印
    fmt.Println("這里有沒有執行?")
}
func test04()  {
    test03()
    fmt.Println("GAME OVER")
}

返回異常

package main

import (
   "errors"
   "fmt"
)

//算半徑
func getCircleArea(radius float32) (ret float32, err error) {
   if radius < 0 {
      //創建異常
      err = errors.New("沙雕,半徑不能為負數")
      return
   }
   ret = 3.14 * radius * radius
   return
}

func main() {
   ret, err := getCircleArea(5)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println("ret=", ret)
   }
}

使用場景:

1)程序遇到了無法正常執行下去的錯誤,主動調用 panic 函數結束程序運行
(2)在調試程序時,通過主動調用 panic 實現快速退出, panic 打印出的堆枝能夠更快地定位錯誤。

為了保證程序的健壯性,需要主動在程序的分支流程上使用 recover()攔截運行時錯誤。

Go 提供了兩種處理錯誤 方式,
一 種是借助 panic和 recover 的拋出捕獲機制,
另一種使用error 錯誤類型

error:

go 語言內置錯誤接口類型
任何類型只要實現 error() string 方法,都可以傳遞 eηor接口類型變量。
Go 語言典型的錯誤處理方式是將error作為函數最后一個返回值 在調用函數 通過檢測其返回的error值是否為nil來進行錯誤處理。
type  error interface{
        Error()  string
}

  

• 在多個返回值的函數中,error 通常作為函數最后一個返回值

• 如果一個函數返回error 類型變量 ,則先用if語句處理 error != nil 異常場景,正常邏輯放到 if 語句塊的后面,
保持代碼平坦。
• defer 吾句應該放到四判斷的后面,不然有可能產生 panic
• 在錯誤逐級向上傳遞的過程中,錯誤信息應該不斷地豐富和完善,而不是簡單地拋出下層調用的錯誤。
這在錯誤日志分析 非常有用和友好。

錯誤和異常

廣義上的錯誤: 發生非期望的行為。
狹義的錯誤:發生非期望的己知行為,這里的己知是指錯誤的類型是預料並定義好的。
異常: 發生非期待的未知行為。這里的未知是指錯誤的類型不在預先定義的范圍內。異常又被稱為未捕獲的錯誤
( untrapped error )。程序在執行時發生未預先定義的錯誤,程序編譯器和運行時都沒有及時將其捕獲處理。
而是由操作系統進行異常處理,比如 語言程序里面經常出現的 Segmentation Fault (段異常錯誤),這個就屬
於異常范疇。

Go 是一門類型安全的語言,其運行時不 出現這種編譯器和運行時都無法捕獲的錯 ,也就是說,
不會出現 untrapped error ,所以從這個角度來說, Go 語言不存在所謂的異常,出現的“異常”全是錯誤

Go 程序需要處理的這些錯誤可 分為兩類
1   一類是運行時錯誤( runtime errors ),此類錯誤語言的運行時能夠捕獲,並采取措施一一隱式或顯式地拋出 panic
2  一類是程序邏輯錯誤:程序執行結果不符合預期,但不會引發運行時錯誤
對於運行時錯誤程序員無法完全避免其發生,只能盡量減少其發生的概率,並在不影響程序主功能的分支流程上
“ rcover ”這些 panic,避免其因為一個panic引發整個程序的崩潰。

Go 對於錯誤提供了兩種處理機制:

(1) 通過函數返回錯誤類型的值來處理錯誤。
(2) 通過 panic 打印程序調用枝,終止程序執行來處理錯誤。
所以對錯誤的處理也有兩種方法,
    一種是通過返回 個錯誤類型值來處理錯誤,
    另一種是直接調用 panic 拋出錯誤,退出程序。
Go 是靜態強類型語言,程序的大部分錯誤是可以在編譯器檢測到的,但是有些錯誤行為需要在運行期才能檢測出來。
此種錯誤行為將導致程序異常退出 。其表現出的行為就和直接調用panic 一樣 打印出函數調用技信息,並且終止程序執行
在實際的編程中,error和panic 的使用應該遵循如下三條原則:
1)程序局部代碼的執行結果不符合預期,但此種行為不是運行時錯誤范圍內預定義的錯
誤,此種非期望的行為不會導致程序無法提供服務,此類場景應該使用函數返回 rror 類型變量
進行錯誤處理。
2)程序執行過程中發生錯誤,且該種錯誤是運行時錯誤范圍內預定義的錯誤,此時 Go
語言默認的隱式處理動作就是調用 panic ,如果此種 panic 發生在程序的分支流程不影響主要更
能,則可以在發生 panic 的程序分支上游處使用 recove 進行捕獲,避免引發整個程序的崩潰。
3)程序局部代碼執行結果不符合預期,此種行為雖然不是運行時錯誤范圍內預定義的錯
誤,但此種非期望的行為會導致程序無法繼續提供服務,此類場景在代碼中應該主動調用 panic
終止程序的執行。
進一步濃縮為兩條規則
(1)程序發生的錯誤導致程序不能容錯繼續執行,此時程序應該主動調用 panic 或由運行
時拋出 panic
(2)程序雖然發生錯誤 但是程序能夠容錯繼續執行,此時應該使用錯誤返回值的方式處理錯誤,
或者在可能發生運行時錯誤的非關鍵分支上使用 recover 捕獲 panic

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM