Golang Recover處理錯(cuò)誤原理解析
引言
Panic/Defer/Recover 基本上是 Golang 中對(duì)于其他編程語(yǔ)言中 throw/finally/catch 概念的替代品。它們有一些共同之處,但在一些重要細(xì)節(jié)上有所不同。
Defer
要充分理解 recover,我們首先需要談?wù)?nbsp;defer 語(yǔ)句。defer ;關(guān)鍵字前置于函數(shù)調(diào)用之前,使得該調(diào)用在當(dāng)前函數(shù)返回之前執(zhí)行。當(dāng)我們?cè)谝粋€(gè)函數(shù)中使用多個(gè) defer 語(yǔ)句時(shí),它們按照后進(jìn)先出的順序執(zhí)行,這使得創(chuàng)建清理邏輯變得非常容易,如下例所示:
package main
import (
"context"
"database/sql"
"fmt"
)
func readRecords(ctx context.Context) error {
db, err := sql.Open("sqlite3", "file:test.db?cache=shared&mode=memory")
if err != nil {
return err
}
defer db.Close() // 這個(gè)函數(shù)調(diào)用將在 readRecords 函數(shù)返回時(shí)第三個(gè)執(zhí)行
conn, err := db.Conn(ctx)
if err != nil {
return err
}
defer conn.Close() // 這個(gè)函數(shù)調(diào)用將在第二個(gè)執(zhí)行
rows, err := conn.QueryContext(ctx, "SELECT id FROM users")
if err != nil {
return err
}
defer rows.Close() // 這個(gè)函數(shù)調(diào)用將在第一個(gè)執(zhí)行
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
return err
}
fmt.Println("ID:", id)
}
return nil
}
func main() {
readRecords(context.Background())
}Panic
我們需要談?wù)摰牡诙€(gè)主題是 panic,它是一個(gè)導(dǎo)致當(dāng)前 goroutine 進(jìn)入 panic 模式的函數(shù)。當(dāng)前函數(shù)中的正常執(zhí)行流程被停止,僅執(zhí)行 defer 語(yǔ)句,然后對(duì)調(diào)用者函數(shù)執(zhí)行相同的操作,因此一直冒泡到堆棧的頂部(main 函數(shù)),然后使程序崩潰。panic 可以直接調(diào)用(傳遞一個(gè)值作為參數(shù)),也可以由運(yùn)行時(shí)錯(cuò)誤引起。例如,由于空指針解引用:
package main
import "fmt"
func main() {
var x *string
fmt.Println(*x)
}
// panic: runtime error: invalid memory address or nil pointer dereferenceRecover
recover 是一個(gè)內(nèi)建函數(shù),它使我們有可能在發(fā)生 panic 時(shí)重新獲得控制。它僅在被調(diào)用的延遲函數(shù)中產(chǎn)生效果。在延遲函數(shù)之外調(diào)用時(shí),它總是返回 nil。如果我們處于 panic 模式,調(diào)用 recover 會(huì)返回傳遞給 panic 函數(shù)的值。基本示例:
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered: %v\\n", r)
}
}()
panic("spam, egg, sausage, and spam")
}
// Recovered: spam, egg, sausage, and spam我們可以以同樣的方式從運(yùn)行時(shí)錯(cuò)誤中恢復(fù):
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered: %v\\n", r)
}
}()
var x *string
fmt.Println(*x)
}
// Recovered: runtime error: invalid memory address or nil pointer dereference在這種情況下,recover 返回的值的類型是錯(cuò)誤(更準(zhǔn)確地說(shuō)是 runtime.errorString)。
有一個(gè)限制:我們不能直接從 recover 塊中返回值,因?yàn)樵?nbsp;recover 塊中的 return 語(yǔ)句僅從延遲函數(shù)中返回,而不是從周圍的函數(shù)中返回:
package main
import "fmt"
func foo() int {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered: %v\\n", r)
return 1 // "too many return values" 因?yàn)槲覀儍H從匿名函數(shù)返回
}
}()
panic("spam, egg, sausage, and spam")
}
func main() {
x := foo()
fmt.Println(x)
}如果我們想要更改函數(shù)返回的值,我們需要使用命名返回值:
package main
import "fmt"
func foo() (ret int) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered: %v\\n", r)
ret = 1
}
}()
panic("spam, egg, sausage, and spam")
}
func main() {
x := foo()
fmt.Println("value:", x)
}
// Recovered: spam, egg, sausage, and spam
// value: 1一個(gè)更實(shí)際的例子,將 panic 轉(zhuǎn)換為普通錯(cuò)誤的轉(zhuǎn)換可能如下所示:
package main
import (
"fmt"
"github.com/google/uuid"
)
// processInput 嘗試將輸入字符串轉(zhuǎn)換為 uuid.UUID
// 它將 panic 轉(zhuǎn)換為錯(cuò)誤
func processInput(input string) (u uuid.UUID, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
// 一些可能引發(fā) panic 的邏輯(也可以是第三方邏輯),例如:
u = uuid.MustParse(input)
return u, nil
}
func main() {
u, err := processInput("xxx")
if err != nil {
fmt.Println(err)
}
fmt.Println(u)
}
// panic: uuid: Parse(xxx): invalid UUID length: 3
// 00000000-0000-0000-0000-000000000000現(xiàn)在讓我們嘗試一些稍微
復(fù)雜的東西。假設(shè)我們?cè)?Kubernetes 中運(yùn)行,并且我們想要編寫(xiě)一個(gè)通用的 recover 函數(shù),處理所有未捕獲的 panic 和運(yùn)行時(shí)錯(cuò)誤,并收集它們的堆棧跟蹤,以便我們可以以結(jié)構(gòu)化的方式記錄它們(例如,以 JSON 格式)。
package main
import (
"fmt"
"log"
"os"
"github.com/pkg/errors"
)
func foo() string {
var s *string
return *s
}
func handlePanic(r interface{}) error {
var errWithStack error
if err, ok := r.(error); ok {
errWithStack = errors.WithStack(err)
} else {
errWithStack = errors.Errorf("%+v", r)
}
return errWithStack
}
func main() {
logger := log.New(os.Stdout, "", 0)
defer func() {
if r := recover(); r != nil {
err := handlePanic(r)
logger.Println(
"panic occurred",
"msg", err.Error(),
"stack", fmt.Sprintf("%+v", err),
)
}
}()
fmt.Println(foo())
}
// 輸出:
// panic occurred msg: runtime error: invalid memory address or nil pointer dereference
// stack: runtime error: invalid memory address or nil pointer dereference
// main.handlePanic
// /tmp/sandbox239055659/prog.go:19
// main.main.func1...以上就是今天的內(nèi)容!recover 函數(shù)并不是 Golang 開(kāi)發(fā)者的日常必備工具,但正如你所看到的,它在某些情況下非常有用,更多關(guān)于Golang Recover錯(cuò)誤處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- GO語(yǔ)言異常處理機(jī)制panic和recover分析
- golang?中?recover()的使用方法
- go語(yǔ)言異常panic和恢復(fù)recover用法實(shí)例
- go語(yǔ)言的panic和recover函數(shù)用法實(shí)例
- Golang中panic與recover的區(qū)別
- Go panic和recover函數(shù)使用細(xì)節(jié)深入探究
- Go中recover與panic區(qū)別詳解
- Golang利用Recover進(jìn)行錯(cuò)誤處理
- golang?recover函數(shù)使用中的一些坑解析
- go使用Cron定時(shí)實(shí)現(xiàn)recover機(jī)制
相關(guān)文章
Go 語(yǔ)言中g(shù)in使用gzip壓縮遇到的問(wèn)題
這篇文章主要介紹了Go 語(yǔ)言中g(shù)in使用gzip壓縮遇到的問(wèn)題,需要的朋友可以參考下2017-09-09
Golang 如何實(shí)現(xiàn)函數(shù)的任意類型傳參
這篇文章主要介紹了Golang 實(shí)現(xiàn)函數(shù)的任意類型傳參操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
Go語(yǔ)言中JWT的創(chuàng)建和解析操作詳解
JWT的全名是Json web token,是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開(kāi)放標(biāo)準(zhǔn),這篇文章主要介紹了在Go語(yǔ)言中JWT的創(chuàng)建和解析操作,感興趣的同學(xué)可以參考下文2023-05-05
golang實(shí)現(xiàn)對(duì)JavaScript代碼混淆
在Go語(yǔ)言中,你可以使用一些工具來(lái)混淆JavaScript代碼,一個(gè)常用的工具是Terser,它可以用于壓縮和混淆JavaScript代碼,你可以通過(guò)Go語(yǔ)言的`os/exec`包來(lái)調(diào)用Terser工具,本文給通過(guò)一個(gè)簡(jiǎn)單的示例給大家介紹一下,感興趣的朋友可以參考下2024-01-01
Golang中interface轉(zhuǎn)string輸出打印方法
這篇文章主要給大家介紹了關(guān)于Golang中interface轉(zhuǎn)string輸出打印的相關(guān)資料,在go語(yǔ)言中interface轉(zhuǎn)string可以直接使用fmt提供的fmt函數(shù),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02
Golang?throttled基于GCRA速率限制庫(kù)使用探索
這篇文章主要為大家介紹了Golang?throttled基于GCRA速率限制庫(kù)使用實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
一文帶你了解Golang中reflect反射的常見(jiàn)錯(cuò)誤
go?反射的錯(cuò)誤大多數(shù)都來(lái)自于調(diào)用了一個(gè)不適合當(dāng)前類型的方法,?而且,這些錯(cuò)誤通常是在運(yùn)行時(shí)才會(huì)暴露出來(lái),而不是在編譯時(shí),如果我們傳遞的類型在反射代碼中沒(méi)有被覆蓋到那么很容易就會(huì)?panic。本文就介紹一下使用?go?反射時(shí)很大概率會(huì)出現(xiàn)的錯(cuò)誤,需要的可以參考一下2023-01-01

