函数与错误处理
Go 的错误处理哲学:错误是值,显式处理。面试主线:defer 机制、panic/recover 边界、错误惯例。
多返回值错误惯例
f, err := os.Open("file.txt")
if err != nil {
return err // 错误向上传播
}
defer f.Close()- Go 没有异常,函数返回 (结果, error),调用方显式检查
- 错误是值:可包装(fmt.Errorf + %w)、可比较、可自定义类型
- 优点:错误路径看得见(不像异常隐式抛出);缺点:代码啰嗦(if err != nil 满天飞)
- 惯例:错误先于结果返回(
func f() (int, error));错误信息小写开头、不以标点结尾(golint ST1005,因为错误常被 %w 包装拼接)
if err := doSomething(); err != nil {
return fmt.Errorf("doSomething: %w", err) // 包装保留链路
}defer:延迟执行
func readFile() error {
f, err := os.Open("x.txt")
if err != nil {
return err
}
defer f.Close() // 函数返回前执行, 无论成败
// 使用 f
return nil
}defer 三考点:
- LIFO 顺序:多个 defer 后进先出(资源释放顺序:先开的后关)
- 参数即时求值:defer 的参数在声明时求值(不是执行时)
i := 0
defer fmt.Println(i) // 打印 0, 不是 1
i++- 执行时机:所在函数返回前执行(return 语句求值后、真正返回前),defer 里可修改返回值(命名返回值时)
panic 与 recover
- panic:运行时严重错误(数组越界、map 并发写),程序崩溃退出(执行所有 defer 后)
- recover:在 defer 里捕获 panic,让程序恢复
func safe() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r) // 捕获, 函数返回, 程序继续
}
}()
panic("boom")
}工程边界(必考):
- panic 用于不可恢复的错误(程序无法继续:初始化失败、内存问题)
- 可预期错误用 error 返回值(文件不存在、参数错)
- recover 只用于进程级兜底(HTTP 中间件捕获 handler panic 返回 500,不让服务崩溃)
- recover 只能在同一 goroutine 的 defer 里生效(其他 goroutine 的 panic 捕获不到)
面试追问
- defer 的执行顺序? LIFO:后声明的先执行。用于资源释放(先开的后关)
- defer 参数何时求值? 声明时(即时求值)。想取执行时值用闭包
- panic 和 error 的分工? 可预期错误用 error,不可恢复用 panic。recover 只在进程级兜底用
- recover 的限制? 必须在本 goroutine 的 defer 里。goroutine 里的 panic 捕获不到,会崩整个进程
- 错误怎么包装? fmt.Errorf + %w,errors.Is/As 解包判断。保留错误链路