【Go系列】 Go的错误处理

avatar
作者
猴君
阅读量:2

承上启下

        上一篇文章中介绍了struct和interface,在Go语言中,是没有Class这个概念的,我们可以通过Struct和方法的组合,来实现Class。我们通过Struct嵌套来实现继承这样的一种机制,并且不用设置虚函数这样的特殊说明。同时,实现接口的方式也是隐式实现,当我们实现了一组接口的所有的方法时,默认一定使用了这组接口了。只能说这样的方法很不直观,但是编译器的静态代码分析还是能够发现的。但是如果是对于程序中的error,Go又会怎么处理呢?

开始学习

错误类型(error

在Go语言中,error 是一个内建的接口类型,用于表示错误状态。它是Go错误处理的核心部分,提供了一种标准的方式来传达函数或方法执行失败的信息。以下是 error 接口的定义:

type error interface {     Error() string } 

这个接口非常简单,只包含一个 Error() 方法,该方法返回一个描述错误的字符串。

创建错误

创建错误通常有以下几种方式:

errors.New:这是最简单的方式,用于创建一个包含给定错误消息的新错误。

err := errors.New("something went wrong") 

​​​​​​fmt.Errorf:这个函数允许你使用格式化字符串创建错误,类似于 fmt.Sprintf

err := fmt.Errorf("invalid argument: %v", value) 

自定义错误类型:你可以通过实现 error 接口来自定义错误类型。

type MyError struct {     Msg string     Code int }  func (e *MyError) Error() string {     return fmt.Sprintf("error %d: %s", e.Code, e.Msg) }  func doSomething() error {     return &MyError{Msg: "operation failed", Code: 42} } 

错误处理

在Go中,错误处理通常是显式的,以下是一些常见的错误处理模式:

检查错误:在调用可能返回错误的函数或方法后,应该立即检查错误。

result, err := doSomething() if err != nil {     // 处理错误     return err } // 使用result 

错误传递:如果当前函数无法处理错误,它应该将错误返回给调用者。

func doSomething() error {     result, err := doAnotherThing()     if err != nil {         return err // 传递错误     }     // 使用result     return nil } 

错误包装:有时,你可能想在返回错误时添加额外的上下文信息。可以使用 fmt.Errorf 和 %w 标志来包装错误。

func doSomething() error {     err := doAnotherThing()     if err != nil {         return fmt.Errorf("failed to do something: %w", err)     }     return nil } 

使用 %w 标志包装的错误可以通过 errors.Is 或 errors.As 进行检查。

错误检查和断言

  • errors.Is:用于检查错误链中是否包含特定错误。
if errors.Is(err, targetErr) {     // err 是 targetErr 或其包装 } 
  • errors.As:用于将错误断言为特定类型。
var targetErr *MyError if errors.As(err, &targetErr) {     // err 是 *MyError 类型 }

Defer函数 

在Go语言中,defer 语句用于确保在函数返回之前执行特定的函数调用(延迟调用)。这对于资源清理、解锁、关闭文件描述符等场景非常有用,因为它可以确保这些操作总是被执行,即使在发生错误或者提前返回的情况下。

defer 语句的语法

defer 语句的语法非常简单:

defer 函数调用 

这里的“函数调用”可以是任何函数或方法调用,包括内置函数、用户定义的函数以及方法。

defer 语句的行为

以下是 defer 语句的一些关键行为:

  1. 延迟执行defer 语句会在包含它的函数即将返回之前执行。无论函数是正常返回还是由于错误返回,defer 语句都会被执行。

  2. 参数评估defer 语句中的参数(如果有的话)是在执行 defer 语句时评估的,而不是在执行延迟函数时。

  3. 执行顺序:如果同一个函数中有多个 defer 语句,它们会按照后进先出(LIFO)的顺序执行。也就是说,最后一个 defer 的函数调用会最先执行。

下面是一个使用 defer 的示例,展示了如何在文件操作后确保文件被关闭:

func main() {     f, err := os.Open("file.txt")     if err != nil {         log.Fatalf("failed to open file: %s", err)     }     defer f.Close() // 延迟关闭文件      // 读取文件内容... } 

在这个例子中,无论后续的文件操作是否成功,f.Close() 都会在 main 函数返回之前被调用。

嵌套 defer

你可以在 defer 语句中调用另一个函数,该函数内部也可以有 defer 语句:

func main() {     defer func() {         // 这里的 defer 会最后执行         defer fmt.Println("Deferred inside defer")         fmt.Println("Outer defer")     }()     fmt.Println("Hello") } 

在这个例子中,输出顺序将是:

Hello Outer defer Deferred inside defer 

这是因为内部的 defer 语句在执行外部的 defer 时被调用,然后才执行外部的 defer 语句。

Recover

在Go语言中,recover 是一个内建函数,它用于捕获运行时的恐慌(panic)。当程序发生恐慌时,正常的函数执行流程会被立即停止,程序将进入恐慌状态,执行所有已注册的延迟函数(deferred functions)。如果在延迟函数中调用了 recover,则可以捕获当前的恐慌,并且使程序恢复正常执行而不是崩溃。

recover 的语法

recover 的语法非常简单,它不接受任何参数,并且返回一个 interface{} 类型的值:

recover() 

如果 recover 被直接调用(不在延迟函数中),它将不会做任何事情,并且返回 nil

recover 的行为

下面是 recover 的一些关键行为:

  1. 只能在延迟函数中生效recover 只能在 defer 语句调用的延迟函数中使用。如果 recover 在其他任何地方被调用,它将不会捕获任何恐慌。

  2. 返回恐慌值:如果当前的goroutine正处于恐慌状态,recover 将捕获到恐慌值,并返回该值,使得程序可以继续执行正常的流程。

  3. 恢复正常执行:一旦 recover 成功捕获了恐慌,程序将不会继续传播恐慌,而是会继续执行延迟函数中的剩余代码,然后返回到恐慌发生点的函数调用者。

下面是一个使用 recover 的示例:

package main  import "fmt"  func main() {     defer func() {         if r := recover(); r != nil {             fmt.Println("Recovered in main:", r)         }     }()     fmt.Println("Start")     panic("Something bad happened")     fmt.Println("End") // 这行代码不会被执行 }  // 输出: // Start // Recovered in main: Something bad happened 

在这个例子中,panic 触发了一个错误,但随后在延迟函数中通过 recover 被捕获。因此,程序没有崩溃,而是打印了恢复信息。

 异常处理对比

  defer 和 recover 是Go语言中用于错误处理的机制,而 try-catch 是许多其他面向对象编程语言(如Java、C++、C#等)中用于异常处理的机制。Go语言最为人所诟病的就是满屏的if err != nil这样的语法,而在其他的高级语言中大多是一个try{}catch{}finally{}直接捕获了所有的代码。这是由于在Go中建议对每个error都单独做处理,如果非要实现try catch的方法,更多时候是通过defer recover实现的。

defer 和 recover

  • 错误处理:在Go中,defer 和 recover 通常用于处理恐慌(panic),这类似于其他语言中的异常。defer 语句用于延迟执行一个函数调用,而 recover 用于捕获恐慌。

  • 使用场景defer 和 recover 通常用于处理运行时错误,这些错误是意外的,并且可能导致程序无法继续执行。

  • 语法

    func someFunction() {     defer func() {         if r := recover(); r != nil {             // 处理恐慌         }     }()     // 可能发生恐慌的代码 } 
  • 显式性defer 和 recover 需要显式地声明,它们不会自动捕获错误。

  • 性能defer 和 recover 的性能开销相对较低,因为它们只在发生恐慌时才执行。

try-catch

  • 异常处理try-catch 用于捕获和处理异常。异常通常表示程序运行时的错误或异常情况。

  • 使用场景try-catch 用于处理各种错误,包括运行时错误和可预见的错误情况。

  • 语法(以Java为例):

    try {     // 可能抛出异常的代码 } catch (SomeException e) {     // 处理异常 } 
  • 自动捕获try-catch 块自动捕获在 try 块中抛出的异常,无需显式声明。

  • 性能try-catch 可能会有更高的性能开销,因为异常的抛出和捕获涉及堆栈跟踪的创建。

对比

  • 错误/异常类型:Go使用 error 类型来表示可预见的错误,而 panic 和 recover 用于处理意外情况。try-catch 通常用于处理所有类型的错误和异常。

  • 传播机制:在Go中,错误必须显式地返回和检查。恐慌会自动传播,直到被 recover 捕获。在 try-catch 机制中,异常会自动向上传播,直到遇到匹配的 catch 块。

  • 资源管理:Go使用 defer 来确保资源被正确释放,而其他语言可能使用 finally 块或RAII(Resource Acquisition Is Initialization)。

  • 控制流try-catch 允许异常改变程序的控制流,而Go的 defer 和 recover 主要用于恢复程序状态,而不是改变控制流。

  • 代码风格:Go鼓励通过返回错误值来进行错误处理,这有助于保持代码的清晰和简洁。try-catch 可能导致代码中异常处理逻辑的分散。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!