MST

星途 面试题库

面试题:Go 复杂业务逻辑下 panic 和 recover 的设计考量

在一个具有复杂业务逻辑和多层嵌套函数调用的 Go 项目中,从代码健壮性、可读性和维护性的角度出发,分析哪些场景适合使用 panic - recover 机制,哪些场景不适合,并且说明如果不适合应采用何种替代方案。请结合实际项目经验详细阐述。
46.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

适合使用 panic - recover 机制的场景

  1. 不可恢复的错误
    • 例如在初始化数据库连接时,如果数据库配置严重错误,如密码错误且无法通过重试等常规手段解决,使用 panic 可以快速终止程序,避免程序在错误状态下继续运行。
    • 代码示例:
package main

import (
    "database/sql"
    _ "github.com/go - sql - driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "user:wrongpassword@tcp(127.0.0.1:3306)/test")
    if err!= nil {
        panic(err)
    }
    defer db.Close()
    // 后续数据库操作
}
  • 原因:这种错误会导致程序无法正常提供预期功能,使用 panic - recover 机制可以快速定位问题,且 recover 可以在程序外层捕获 panic,进行日志记录等善后操作。
  1. 违反程序内部不变式
    • 比如在实现一个栈数据结构时,如果代码逻辑要求栈不能为空才能执行弹出操作,但在某个地方由于逻辑错误,栈为空时却执行了弹出操作,此时可以 panic
    • 代码示例:
package main

import (
    "fmt"
)

type Stack struct {
    data []int
}

func (s *Stack) Pop() int {
    if len(s.data) == 0 {
        panic("pop from empty stack")
    }
    top := s.data[len(s.data)-1]
    s.data = s.data[:len(s.data)-1]
    return top
}

func main() {
    var s Stack
    s.Pop()
}
  • 原因:不变式被打破意味着程序处于一个不应该出现的状态,使用 panic 可以立即暴露问题,方便调试和修复。

不适合使用 panic - recover 机制的场景

  1. 业务逻辑错误
    • 例如在电商系统中,用户下单时库存不足,这是业务层面可预期的情况,不应该 panic
    • 不适合原因:panic - recover 机制不是用于处理业务流程中的常规错误,使用它会使代码的业务逻辑变得混乱,难以理解和维护。
    • 替代方案:返回错误值。
    • 代码示例:
package main

import (
    "fmt"
)

func Order(productID int, quantity int) error {
    // 假设这里从数据库获取库存
    stock := getStock(productID)
    if stock < quantity {
        return fmt.Errorf("not enough stock for product %d", productID)
    }
    // 进行下单逻辑,如扣除库存等
    return nil
}

func getStock(productID int) int {
    // 模拟从数据库获取库存
    return 10
}

func main() {
    err := Order(1, 15)
    if err!= nil {
        fmt.Println(err)
    }
}
  1. 可重试的错误
    • 比如在调用外部 API 时,偶尔因为网络波动等原因失败,这种情况不适合 panic
    • 不适合原因:panic 会终止当前 goroutine 的正常执行流程,对于可重试的错误,我们希望程序能够通过重试继续运行。
    • 替代方案:使用重试逻辑结合错误处理。
    • 代码示例:
package main

import (
    "fmt"
    "time"
)

func CallAPI() error {
    // 模拟 API 调用失败
    return fmt.Errorf("API call failed")
}

func main() {
    maxRetries := 3
    for i := 0; i < maxRetries; i++ {
        err := CallAPI()
        if err == nil {
            fmt.Println("API call success")
            break
        }
        fmt.Printf("Retry %d: API call failed, error: %v\n", i+1, err)
        time.Sleep(time.Second)
    }
}