与其他主流编程语言(例如 JavaScript(使用语句try… catch)或 Python(及其try… except块))中的传统方法不同,Go 中处理错误需要不同的方法。为什么?因为它的错误处理功能经常被误用。
在这篇博文中,我们将了解可用于处理 Go 应用程序中的错误的最佳实践。消化本文所需的只是对Go(https://golang.org/doc/)工作原理的基本了解- 如果您在某些时候感到陷入困境,可以花一些时间研究不熟悉的概念。
空白标识符
空白标识符(https://golang.org/doc/effective_go.html#blank)是匿名占位符。它可以像声明中的任何其他标识符一样使用,但它不引入绑定。空白标识符提供了一种忽略赋值中的左手值并避免有关程序中未使用的导入和变量的编译器错误的方法。将错误分配给空白标识符而不是正确处理它们的做法是不安全的,因为这意味着您决定显式忽略已定义函数的值。
result, _ := iterate(x,y) if value > 0 { // 确保在结果之前检查错误。 }
您这样做的原因可能是您不希望函数出现错误(或可能发生的任何错误),但这可能会在您的程序中产生级联效应。最好的办法是尽可能处理错误。
通过多个返回值处理错误
处理错误的一种方法是利用 Go 中的函数支持多个返回值这一事实。因此,您可以将错误变量与您定义的函数的结果一起传递:
func iterate(x, y int) (int, error) { }
error在上面的代码示例中,如果我们认为函数有可能失败,则必须返回预定义变量。error是 Go 包中声明的接口类型built-in,其零值为nil。
type error interface { Error() string }
通常,返回错误意味着有问题,返回nil意味着没有错误:
result, err := iterate(x, y) if err != nil { // 适当处理错误 } else { // 你可以走了 }
因此,每当函数iterate被调用并且err不等于 时nil,返回的错误都应该得到适当的处理——一个选项可以是创建重试或清理机制的实例。以这种方式处理错误的唯一缺点是 Go 编译器没有强制执行,您必须决定您创建的函数如何返回错误。您可以定义一个错误结构并将其放置在返回值的位置。一种方法是使用内置结构体(您也可以在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 。要创建自定义错误,您必须定义错误结构并使用方法集将函数与结构关联起来:stringError
// 定义一个错误结构体 type CustomError struct { msg string } // 创建一个函数 Error() 字符串并将其关联到结构体。 func(error * CustomError) Error() string { return error.msg } // 然后使用 MyError 结构创建一个错误对象。 func CustomErrorInstance() error { return &CustomError { "File type not supported" } }
然后可以重组新创建的自定义错误以使用内置error结构:
import "errors" func CustomeErrorInstance() error { return errors.New("File type not supported") }
内置error结构的一个限制是它不带有堆栈跟踪。这使得定位错误发生的位置变得非常困难。该错误在打印出来之前可能会经过多个函数。为了解决这个问题,您可以安装pkg/errors提供基本错误处理原语的包,例如堆栈跟踪记录、错误包装、展开和格式化。要安装此软件包,请在终端中运行以下命令:
go get github.com/pkg/errors
当您需要添加堆栈跟踪或任何其他信息以便更轻松地调试错误时,请使用 或 函数New来Errorf提供记录堆栈跟踪的错误。Errorf实现fmt.Formatter允许您使用fmt包 runes(%s、等)格式化错误的接口%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()) }
要打印堆栈跟踪而不是普通的错误消息,您必须在格式模式中使用%+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
推迟、恐慌和恢复
尽管 Go 没有例外,但它有一种类似的机制,称为“延迟、恐慌和恢复”。Go 的理念是,添加异常(例如try/catch/finallyJavaScript 中的语句)会导致代码复杂,并鼓励程序员将太多基本错误(例如无法打开文件)标记为异常。你不应该defer/panic/recover像你想的那样使用throw/catch/finally;仅在意外的、不可恢复的故障的情况下。
Defer是一种将函数调用放入堆栈的语言机制。当主机函数完成时,无论是否调用恐慌,每个延迟函数都会以相反的顺序执行。defer机制对于清理资源非常有用:
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是一个停止正常执行流程的内置函数。当您调用panic代码时,这意味着您已经确定调用者无法解决问题。因此,panic仅应在极少数情况下使用,在这种情况下,您的代码或集成您的代码的任何人在此时继续不安全。下面是描述工作原理的代码示例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.
如上所示,当panic使用但未处理时,执行流程停止,所有延迟函数按相反顺序执行,并打印堆栈跟踪。
您可以使用recover内置函数来处理panic和返回从紧急调用传递的值。recover必须始终在函数中调用,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这可以防止整个执行流程停止,因为我们放入一个panic函数,编译器将返回:
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.
要将错误报告为返回值,您必须在调用recover函数的同一goroutine中调用该函数panic,从函数中检索错误结构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) }
每个延迟函数都将在函数调用之后但 return 语句之前执行。因此,您可以在执行 return 语句之前设置返回变量。上面的代码示例将编译为:
If it's more than 100 degrees... Then there's nothing we can doProgram exited.
错误换行
以前,Go 中的错误包装只能通过使用pkg/errors. 然而,Go 的最新版本 -版本 1.13提供了对错误包装的支持。根据发行说明:
一个错误可以通过提供返回 的方法来e包装另一个错误。和都可供程序使用,允许提供额外的上下文或重新解释它,同时仍然允许程序基于 做出决策。wUnwrapweweww
为了创建包装错误,fmt.Errorf现在有一个%w动词,并且为了检查和展开错误,已在包中添加了几个函数error:
errors.Unwrap:该函数主要检查并暴露程序中的潜在错误。Unwrap它返回调用上的方法的结果Err。如果 Err 的类型包含Unwrap返回错误的方法。否则,Unwrap返回nil。
package errors type Wrapper interface{ Unwrap() error }
下面是该方法的示例实现Unwrap:
func(e*PathError)Unwrap()error{ return e.Err }
errors.Is:使用此功能,您可以将错误值与哨兵值进行比较。该函数与我们通常的错误检查的不同之处在于,它不是将哨兵值与一个错误进行比较,而是将其与错误链中的每个错误进行比较。它还实现了一个Is错误方法,以便错误可以将自己作为哨兵发布,即使它不是哨兵值。
func Is(err, target error) bool
在上面的基本实现中,Is检查并报告其链中的err任何一个是否errors等于目标(哨兵值)。
errors.As:该函数提供了一种转换为特定错误类型的方法。它查找错误链中与哨兵值匹配的第一个错误,如果找到,则将哨兵值设置为该错误值并返回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)
编译结果:
Failed at path: non-existing Program exited.
如果错误的具体值可分配给哨兵值指向的值,则错误与哨兵值匹配。As如果哨兵值不是指向实现错误的类型或任何接口类型的非零指针,则会出现恐慌。如果是As则返回 false 。errnil
概括
Go 社区最近在支持各种编程概念并引入更简洁、更简单的错误处理方法方面取得了令人印象深刻的进步。您对如何处理 Go 程序中可能出现的错误有任何想法吗?请在下面的评论中告诉我。
资源:
关于类型断言的 Go 编程语言规范(https://golang.org/ref/spec#Type_assertions)
Go 1.13 发行说明(https://golang.org/doc/go1.13)文章来源:https://www.toymoban.com/diary/golang/346.html
文章来源地址https://www.toymoban.com/diary/golang/346.html
到此这篇关于如何处理Golang中的错误的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!