Go语言的错误处理机制

Go语言的错误处理机制

recover

  • 每次defer都会将defer函数压入栈中,调用函数或者方法结束时,从栈中取出执行,所以多个defer的执行顺序是先入后出。

  • recover只能在defer函数中捕获异常,单独出现没有意义,发生异常的函数停止执行,其余函数继续执行。

  • defer、return、返回值三者的执行顺序是:

    1. 先给返回值赋值
    2. 执行defer语句
    3. 包裹函数return返回
    go 复制代码
    func f() (r int) {
         t := 5
         defer func() {
           r = r + 5  // 直接修改返回值变量r
         }()
         return t     // 第一步r=5 → defer执行r=10 → 返回10
    }
    // 等价于
    	func f() (r int) {
         t := 5	// 第1步
         r = t		// 第2步
         defer func() {
           r = r + 5  // 第3步
         }()
         return     // 第4步
    }
    // f()执行结果为t->5

代码示例

go 复制代码
package main

import (
	"fmt"
)

func test() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Recovered:", err)
		}
	}()
	a, b := 1, 0
	fmt.Println(a / b)
	fmt.Println("Error After")
}

func main() {
	fmt.Println("Start")
	test()
	fmt.Println("End")
}

执行结果如下:

powershell 复制代码
Start
Recovered: runtime error: integer divide by zero
End

代码解析

  1. fmt.Println("Start")程序开始;
  2. 进入函数test();执行到fmt.Println(a / b),除数不能为0,所以该错误会立即被recover捕获,进入到defer函数中,该处打印错误信息Recovered: runtime error: integer divide by zero;此时test()函数结束执行,fmt.Println(a / b)下方的fmt.Println("Error After")不会被执行;
  3. fmt.Println("End")程序结束;

多唠两句

recover并不能阻止错误发生,而是让程序「不崩溃」,并能处理错误后继续运行。

panic

  1. 它会立即终止当前 goroutine 的正常代码执行流程;
  2. 如果没有 recover 兜底,会导致整个程序崩溃退出;
  3. 可以理解为:程序运行中遇到了 "没法继续往前走" 的致命问题,只能 "喊停"。
  4. 包含两大核心场景:Go 语言自动触发、开发者手动触发

panic 的执行机制

  1. 立即终止当前行及后续的正常代码;
  2. 倒序执行当前函数中已声明的 defer 语句(后进先出);
  3. 向上传递到调用方函数,重复步骤 1-2(相当于 "逐层回退");
  4. 如果整个调用链中没有 recover,程序最终崩溃退出。

代码示例

go 复制代码
package main

import "fmt"

// 普通error:参数错误(可预期)
func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, fmt.Errorf("除数不能为0") // 返回error,不终止程序
	}
	return a / b, nil
}

// panic:空指针(不可恢复)
func nilPointer() {
	var p *int
	*p = 10 // 自动触发panic
}

func main() {
	// 处理error
	res, err := divide(10, 0)
	if err != nil {
		fmt.Println("处理error:", err) // 输出:处理error:除数不能为0
	} else {
		fmt.Println("结果:", res)
	}

	// 处理panic
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("处理panic:", err) // 输出:处理panic: runtime error: invalid memory address or nil pointer dereference
		}
	}()
	nilPointer()

	// 处理完panic后,程序继续运行
	fmt.Println("程序未崩溃,正常结束")
}

执行结果如下:

powershell 复制代码
处理error: 除数不能为0
处理panic: runtime error: invalid memory address or nil pointer dereference
程序未崩溃,正常结束

多唠两句

  • 尽量少用手动 panic:业务逻辑优先用 error 处理,panic 只留给 "程序没法运行" 的极端场景;
  • recover 只在顶层函数兜底:比如 HTTP 服务的中间件、gRPC 拦截器、main 函数,避免在底层函数滥用 recover(会隐藏问题);
  • recover 后记录日志:捕获 panic 时,一定要记录完整的堆栈信息(用 debug.Stack()),方便排查问题;
  • 不要用 panic 替代 error:比如用户输入错误、接口调用失败,用 error 更优雅。
相关推荐
飘尘1 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
浏览器工程师2 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
行者全栈架构师2 小时前
Maven dependency:tree 的 8 个高级用法
java·后端
Chenyiax2 小时前
从一次请求看懂 OkHttp:架构、调度与连接管理
后端
爱勇宝3 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
AskHarries4 小时前
工具失败时怎么办:重试、回滚、人工确认和风险提示
后端·程序员
苏三说技术5 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎6 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode6 小时前
Redis 在生产项目的使用
前端·后端
用户559822481226 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端