跳至主要內容

异常

LincZero大约 4 分钟

异常

网上有吐槽 Go 的异常处理不是很好,原因暂时不知

异常的处理主要使用:

  • 内置
    • defer
    • recover
  • errors包
    • errors.New
    • errors.panic

异常

package main
import "fmt"

func main() {
    div(10, 5)		// 能执行成功
    div(10, 0)		// 会报错:panic: runtime error: integer divide by zero
}

func div(num1 int, num2 int) {
    result := num1 / num2
    fmt.Println(result)
}

异常类型

  • 特别需要注意:Go有一种异常为 “panic”,翻译过来叫 “恐慌”,有点怪怪的。这种异常可以通过 recover 恢复(类似其他语言的try-catch)

defer + recover 机制处理错误

defer 基本用法 (在函数中)

与其他语言不同:Go特有。

不同语言的类似处理:

  • goto方法

  • C语言

    • Setjmp/Longjmp

      可以用来实现异常处理和资源释放。setjmp用于捕获当前环境的上下文,而longjmp则用于从setjmp返回,并可以选择性地执行资源释放代码。

      这种方法可以在异常发生时跳转到函数的末尾执行清理代码,而不需要使用大量的if语句。

  • C++

    • RAII (Resource Acquisition Is Initialization)

      与智能指针(如std::unique_ptr或std::shared_ptr)一起使用。当对象被创建时,它会获取资源;当对象被销毁时,它会自动释放资源。

  • Java的 try-with-resources/try-catch-finally

    FileWriter writer = null;
    try {
        writer = new FileWriter("example.txt");
        // 使用writer对象
        // ...
    } catch (IOException e) {
        // 处理异常
        // ...
    } finally {
        if (writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                // 处理关闭资源时的异常
                // ...
            }
        }
    }
    
  • Python

    上下文管理器 __exit____enter__with

    __exit__方法定义了退出with语句块时的行为,通常用于释放资源

    class MyResource:
        def __enter__(self):
            # 获取资源
            return self
    
        def __exit__(self, exc_type, exc_value, traceback):
            # 释放资源
            if exc_type:
                # 如果有异常发生,可以选择处理或忽略
                print(f"An exception occurred: {exc_type}, {exc_value}")
            # 总是执行资源释放
            # ...
    
    with MyResource() as resource:
        # 使用资源
        # ...
        
    # 上面是自己定义的情况。一般用得比较多的是打开文件的 open with,无需显式调用 close() 方法
    
  • C#

    • using语句,类似于Java的try-with-resources
  • JavaScript

    • Promise和async/await
  • Go语言

    • 函数内处理异常:Go使用defer关键字用于处理函数的资源释放,以及异常处理等(关于defer的更多信息,详见Go语言的 “异常” 部分) Go 追求代码优雅,不使用 try-catch 机制,而是使用 defer+recover 机制
      • defer关键字:可以在函数返回前执行一段代码,通常用于资源释放。
      • recover关键字:允许程序管理恐慌过程。recover() 可以捕获异常,同时使程序恢复正常,停止恐慌过程
      • 结合 try-defer 模式,可以在发生异常时自动释放资源,而无需在每个判断点重复编写释放代码
    • 函数外处理异常:不使用 “抛出” 异常,由于Go可以多返回值,只需要 “返回” 异常即可

recover

内置函数 recover

作用:允许程序管理恐慌过程。recover() 可以捕获异常,同时使程序恢复正常,停止恐慌过程

defer + recover 处理异常

package main
import "fmt"

func main() {
    div(10, 5)		// 能执行成功
    div(10, 0)		// 会报错:panic: runtime error: integer divide by zero
}

func div(num1 int, num2 int) {
    // defer+recover 捕获错误
    defer func() {			// 这里是一个匿名的立刻执行函数
        err := recover()	// 如果没有捕获错误,返回值为零值:nil
        if err != nil {
            fmt.Println("错误捕获了, 异常为:", err)
        }
    }()
    result := num1 / num2
    fmt.Println(result)
}

异常处理和错误处理

看课看到异常部分时有个弹幕:大哥你这是异常处理不是错误处理,error和panic都没搞清楚。于是我搜了下

  • 其他语言:异常处理是通过 try-catch-finally 这样的结构来实现的。例如,在Java或C#中,你可以使用这些关键字来捕获和处理异常
  • Go语言:在某些编程语言中,特别是Go语言,errorpanic是两种不同的机制,用于处理不同类型的问题
    • error:是一个接口类型,用于表示错误情况。当函数执行失败时,它通常会返回一个error对象,调用者可以检查这个对象来确定是否发生了错误,并获取错误的详细信息
    • panic:是一个内置函数,用于在发生严重错误时终止程序的执行。这通常用于那些不应该发生的情况,比如内部一致性被破坏、严重的逻辑错误等。当panic被调用时,当前的函数会立即停止执行,跳转到最近的defer语句(如果有的话),然后程序会打印堆栈跟踪信息并退出。

自定义错误 errors包.New()

不同与其他语言:由于Go有多返回值的特性,并不需要像其他语言那样 “抛出” 异常,而仅需 “返回” 异常。这点不错

package main
import (
    "fmt"
    "errors"
)

func main() {
    err := div(10, 5)
    if err != nil {
        fmt.Println("自定义错误: ", err)
    }
    err = div(10, 0)
    if err != nil {
        fmt.Println("自定义错误: ", err)
    }
}

func div(num1 int, num2 int) (err error) { 
    if num2 == 0 {
        // 抛出自定义异常
        return errors.New("除数不能为0")
    } else {
        result := num1 / num2
        fmt.Println(result)
        return nil
    }
}

终止程序 errors.panic

有时程序出现错误时,应该立即终止

func panic(v interface{})

// 使用
err := test()
if err != nil {
    fmt.Println("自定义错误: ", err)
    panic(err)
}