與其他主流編程語(yǔ)言(例如 JavaScript(使用語(yǔ)句try… catch)或 Python(及其try… except塊))中的傳統(tǒng)方法不同,Go 中處理錯(cuò)誤需要不同的方法。為什么?因?yàn)樗腻e(cuò)誤處理功能經(jīng)常被誤用。
在這篇博文中,我們將了解可用于處理 Go 應(yīng)用程序中的錯(cuò)誤的最佳實(shí)踐。消化本文所需的只是對(duì)Go(https://golang.org/doc/)工作原理的基本了解- 如果您在某些時(shí)候感到陷入困境,可以花一些時(shí)間研究不熟悉的概念。
空白標(biāo)識(shí)符
空白標(biāo)識(shí)符(https://golang.org/doc/effective_go.html#blank)是匿名占位符。它可以像聲明中的任何其他標(biāo)識(shí)符一樣使用,但它不引入綁定??瞻讟?biāo)識(shí)符提供了一種忽略賦值中的左手值并避免有關(guān)程序中未使用的導(dǎo)入和變量的編譯器錯(cuò)誤的方法。將錯(cuò)誤分配給空白標(biāo)識(shí)符而不是正確處理它們的做法是不安全的,因?yàn)檫@意味著您決定顯式忽略已定義函數(shù)的值。
result, _ := iterate(x,y) if value > 0 { // 確保在結(jié)果之前檢查錯(cuò)誤。 }
您這樣做的原因可能是您不希望函數(shù)出現(xiàn)錯(cuò)誤(或可能發(fā)生的任何錯(cuò)誤),但這可能會(huì)在您的程序中產(chǎn)生級(jí)聯(lián)效應(yīng)。最好的辦法是盡可能處理錯(cuò)誤。
通過(guò)多個(gè)返回值處理錯(cuò)誤
處理錯(cuò)誤的一種方法是利用 Go 中的函數(shù)支持多個(gè)返回值這一事實(shí)。因此,您可以將錯(cuò)誤變量與您定義的函數(shù)的結(jié)果一起傳遞:
func iterate(x, y int) (int, error) { }
error在上面的代碼示例中,如果我們認(rèn)為函數(shù)有可能失敗,則必須返回預(yù)定義變量。error是 Go 包中聲明的接口類型built-in,其零值為nil。
type error interface { Error() string }
通常,返回錯(cuò)誤意味著有問(wèn)題,返回nil意味著沒(méi)有錯(cuò)誤:
result, err := iterate(x, y) if err != nil { // 適當(dāng)處理錯(cuò)誤 } else { // 你可以走了 }
因此,每當(dāng)函數(shù)iterate被調(diào)用并且err不等于 時(shí)nil,返回的錯(cuò)誤都應(yīng)該得到適當(dāng)?shù)奶幚怼粋€(gè)選項(xiàng)可以是創(chuàng)建重試或清理機(jī)制的實(shí)例。以這種方式處理錯(cuò)誤的唯一缺點(diǎn)是 Go 編譯器沒(méi)有強(qiáng)制執(zhí)行,您必須決定您創(chuàng)建的函數(shù)如何返回錯(cuò)誤。您可以定義一個(gè)錯(cuò)誤結(jié)構(gòu)并將其放置在返回值的位置。一種方法是使用內(nèi)置結(jié)構(gòu)體(您也可以在Go 的源代碼(https://golang.org/src/errors/errors.go)errorString中找到此代碼):
package errors func New(text string) error { return &errorString { text } } type errorString struct { s string } func(e * errorString) Error() string { return e.s }
在上面的代碼示例中,嵌入了該方法返回的errorStringa 。要?jiǎng)?chuàng)建自定義錯(cuò)誤,您必須定義錯(cuò)誤結(jié)構(gòu)并使用方法集將函數(shù)與結(jié)構(gòu)關(guān)聯(lián)起來(lái):stringError
// 定義一個(gè)錯(cuò)誤結(jié)構(gòu)體 type CustomError struct { msg string } // 創(chuàng)建一個(gè)函數(shù) Error() 字符串并將其關(guān)聯(lián)到結(jié)構(gòu)體。 func(error * CustomError) Error() string { return error.msg } // 然后使用 MyError 結(jié)構(gòu)創(chuàng)建一個(gè)錯(cuò)誤對(duì)象。 func CustomErrorInstance() error { return &CustomError { "File type not supported" } }
然后可以重組新創(chuàng)建的自定義錯(cuò)誤以使用內(nèi)置error結(jié)構(gòu):
import "errors" func CustomeErrorInstance() error { return errors.New("File type not supported") }
內(nèi)置error結(jié)構(gòu)的一個(gè)限制是它不帶有堆棧跟蹤。這使得定位錯(cuò)誤發(fā)生的位置變得非常困難。該錯(cuò)誤在打印出來(lái)之前可能會(huì)經(jīng)過(guò)多個(gè)函數(shù)。為了解決這個(gè)問(wèn)題,您可以安裝pkg/errors提供基本錯(cuò)誤處理原語(yǔ)的包,例如堆棧跟蹤記錄、錯(cuò)誤包裝、展開(kāi)和格式化。要安裝此軟件包,請(qǐng)?jiān)诮K端中運(yùn)行以下命令:
go get github.com/pkg/errors
當(dāng)您需要添加堆棧跟蹤或任何其他信息以便更輕松地調(diào)試錯(cuò)誤時(shí),請(qǐng)使用 或 函數(shù)New來(lái)Errorf提供記錄堆棧跟蹤的錯(cuò)誤。Errorf實(shí)現(xiàn)fmt.Formatter允許您使用fmt包 runes(%s、等)格式化錯(cuò)誤的接口%v:%+v
import( "github.com/pkg/errors" "fmt" ) func X() error { return errors.Errorf("Could not write to file") } func customError() { return X() } func main() { fmt.Printf("Error: %+v", customError()) }
要打印堆棧跟蹤而不是普通的錯(cuò)誤消息,您必須在格式模式中使用%+v 而不是%v,堆棧跟蹤將類似于下面的代碼示例:
Error: Could not write to file main.X /Users/raphaelugwu/Go/src/golangProject/error_handling.go:7 main.customError /Users/raphaelugwu/Go/src/golangProject/error_handling.go:15 main.main /Users/raphaelugwu/Go/src/golangProject/error_handling.go:19 runtime.main /usr/local/opt/go/libexec/src/runtime/proc.go:192 runtime.goexit /usr/local/opt/go/libexec/src/runtime/asm_amd64.s:2471
推遲、恐慌和恢復(fù)
盡管 Go 沒(méi)有例外,但它有一種類似的機(jī)制,稱為“延遲、恐慌和恢復(fù)”。Go 的理念是,添加異常(例如try/catch/finallyJavaScript 中的語(yǔ)句)會(huì)導(dǎo)致代碼復(fù)雜,并鼓勵(lì)程序員將太多基本錯(cuò)誤(例如無(wú)法打開(kāi)文件)標(biāo)記為異常。你不應(yīng)該defer/panic/recover像你想的那樣使用throw/catch/finally;僅在意外的、不可恢復(fù)的故障的情況下。
Defer是一種將函數(shù)調(diào)用放入堆棧的語(yǔ)言機(jī)制。當(dāng)主機(jī)函數(shù)完成時(shí),無(wú)論是否調(diào)用恐慌,每個(gè)延遲函數(shù)都會(huì)以相反的順序執(zhí)行。defer機(jī)制對(duì)于清理資源非常有用:
package main import ( "fmt" ) func A() { defer fmt.Println("Keep calm!") B() } func B() { defer fmt.Println("Else...") C() } func C() { defer fmt.Println("Turn on the air conditioner...") D() } func D() { defer fmt.Println("If it's more than 30 degrees...") } func main() { A() }
這將編譯為:
If it's more than 30 degrees... Turn on the air conditioner... Else... Keep calm!
Panic是一個(gè)停止正常執(zhí)行流程的內(nèi)置函數(shù)。當(dāng)您調(diào)用panic代碼時(shí),這意味著您已經(jīng)確定調(diào)用者無(wú)法解決問(wèn)題。因此,panic僅應(yīng)在極少數(shù)情況下使用,在這種情況下,您的代碼或集成您的代碼的任何人在此時(shí)繼續(xù)不安全。下面是描述工作原理的代碼示例panic:
package mainimport ( package main import ( "errors" "fmt" ) func A() { defer fmt.Println("Then we can't save the earth!") B() } func B() { defer fmt.Println("And if it keeps getting hotter...") C() } func C() { defer fmt.Println("Turn on the air conditioner...") Break() } func Break() { defer fmt.Println("If it's more than 30 degrees...") panic(errors.New("Global Warming!!!")) } func main() { A() }
上面的示例將編譯為:
If it's more than 30 degrees... Turn on the air conditioner... And if it keeps getting hotter... Then we can't save the earth! panic: Global Warming!!! goroutine 1 [running]: main.Break() /tmp/sandbox186240156/prog.go:22 +0xe0 main.C() /tmp/sandbox186240156/prog.go:18 +0xa0 main.B() /tmp/sandbox186240156/prog.go:14 +0xa0 main.A() /tmp/sandbox186240156/prog.go:10 +0xa0 main.main() /tmp/sandbox186240156/prog.go:26 +0x20 Program exited: status 2.
如上所示,當(dāng)panic使用但未處理時(shí),執(zhí)行流程停止,所有延遲函數(shù)按相反順序執(zhí)行,并打印堆棧跟蹤。
您可以使用recover內(nèi)置函數(shù)來(lái)處理panic和返回從緊急調(diào)用傳遞的值。recover必須始終在函數(shù)中調(diào)用,defer否則它將返回nil:
package main import ( "errors" "fmt" ) func A() { defer fmt.Println("Then we can't save the earth!") defer func() { if x := recover(); x != nil { fmt.Printf("Panic: %+v\n", x) } }() B() } func B() { defer fmt.Println("And if it keeps getting hotter...") C() } func C() { defer fmt.Println("Turn on the air conditioner...") Break() } func Break() { defer fmt.Println("If it's more than 30 degrees...") panic(errors.New("Global Warming!!!")) } func main() { A() }
從上面的代碼示例中可以看出,recover這可以防止整個(gè)執(zhí)行流程停止,因?yàn)槲覀兎湃胍粋€(gè)panic函數(shù),編譯器將返回:
If it's more than 30 degrees... Turn on the air conditioner... And if it keeps getting hotter... Panic: Global Warming!!! Then we can't save the earth!Program exited.
要將錯(cuò)誤報(bào)告為返回值,您必須在調(diào)用recover函數(shù)的同一goroutine中調(diào)用該函數(shù)panic,從函數(shù)中檢索錯(cuò)誤結(jié)構(gòu)recover,并將其傳遞給變量:
package main import ( "errors" "fmt" ) func saveEarth() (err error) { defer func() { if r := recover(); r != nil { err = r.(error) } }() TooLate() return } func TooLate() { A() panic(errors.New("Then there's nothing we can do")) } func A() { defer fmt.Println("If it's more than 100 degrees...") } func main() { err := saveEarth() fmt.Println(err) }
每個(gè)延遲函數(shù)都將在函數(shù)調(diào)用之后但 return 語(yǔ)句之前執(zhí)行。因此,您可以在執(zhí)行 return 語(yǔ)句之前設(shè)置返回變量。上面的代碼示例將編譯為:
If it's more than 100 degrees... Then there's nothing we can doProgram exited.
錯(cuò)誤換行
以前,Go 中的錯(cuò)誤包裝只能通過(guò)使用pkg/errors. 然而,Go 的最新版本 -版本 1.13提供了對(duì)錯(cuò)誤包裝的支持。根據(jù)發(fā)行說(shuō)明:
一個(gè)錯(cuò)誤可以通過(guò)提供返回 的方法來(lái)e包裝另一個(gè)錯(cuò)誤。和都可供程序使用,允許提供額外的上下文或重新解釋它,同時(shí)仍然允許程序基于 做出決策。wUnwrapweweww
為了創(chuàng)建包裝錯(cuò)誤,fmt.Errorf現(xiàn)在有一個(gè)%w動(dòng)詞,并且為了檢查和展開(kāi)錯(cuò)誤,已在包中添加了幾個(gè)函數(shù)error:
errors.Unwrap:該函數(shù)主要檢查并暴露程序中的潛在錯(cuò)誤。Unwrap它返回調(diào)用上的方法的結(jié)果Err。如果 Err 的類型包含Unwrap返回錯(cuò)誤的方法。否則,Unwrap返回nil。
package errors type Wrapper interface{ Unwrap() error }
下面是該方法的示例實(shí)現(xiàn)Unwrap:
func(e*PathError)Unwrap()error{ return e.Err }
errors.Is:使用此功能,您可以將錯(cuò)誤值與哨兵值進(jìn)行比較。該函數(shù)與我們通常的錯(cuò)誤檢查的不同之處在于,它不是將哨兵值與一個(gè)錯(cuò)誤進(jìn)行比較,而是將其與錯(cuò)誤鏈中的每個(gè)錯(cuò)誤進(jìn)行比較。它還實(shí)現(xiàn)了一個(gè)Is錯(cuò)誤方法,以便錯(cuò)誤可以將自己作為哨兵發(fā)布,即使它不是哨兵值。
func Is(err, target error) bool
在上面的基本實(shí)現(xiàn)中,Is檢查并報(bào)告其鏈中的err任何一個(gè)是否errors等于目標(biāo)(哨兵值)。
errors.As:該函數(shù)提供了一種轉(zhuǎn)換為特定錯(cuò)誤類型的方法。它查找錯(cuò)誤鏈中與哨兵值匹配的第一個(gè)錯(cuò)誤,如果找到,則將哨兵值設(shè)置為該錯(cuò)誤值并返回true:
package main import ( "errors" "fmt" "os" ) func main() { if _, err := os.Open("non-existing"); err != nil { var pathError *os.PathError if errors.As(err, &pathError) { fmt.Println("Failed at path:", pathError.Path) } else { fmt.Println(err) } } }
你可以在Go的源代碼中找到這段代碼。(https://golang.org/pkg/errors/#As)
編譯結(jié)果:
Failed at path: non-existing Program exited.
如果錯(cuò)誤的具體值可分配給哨兵值指向的值,則錯(cuò)誤與哨兵值匹配。As如果哨兵值不是指向?qū)崿F(xiàn)錯(cuò)誤的類型或任何接口類型的非零指針,則會(huì)出現(xiàn)恐慌。如果是As則返回 false 。errnil
概括
Go 社區(qū)最近在支持各種編程概念并引入更簡(jiǎn)潔、更簡(jiǎn)單的錯(cuò)誤處理方法方面取得了令人印象深刻的進(jìn)步。您對(duì)如何處理 Go 程序中可能出現(xiàn)的錯(cuò)誤有任何想法嗎?請(qǐng)?jiān)谙旅娴脑u(píng)論中告訴我。
資源:
關(guān)于類型斷言的 Go 編程語(yǔ)言規(guī)范(https://golang.org/ref/spec#Type_assertions)
Go 1.13 發(fā)行說(shuō)明(https://golang.org/doc/go1.13)文章來(lái)源:http://www.zghlxwxcb.cn/article/346.html
文章來(lái)源地址http://www.zghlxwxcb.cn/article/346.html
到此這篇關(guān)于如何處理Golang中的錯(cuò)誤的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!