news 2026/8/25 6:55:50

Go 语言入门笔记(八):错误处理与 panic/recover——Go 的“异常”哲学

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go 语言入门笔记(八):错误处理与 panic/recover——Go 的“异常”哲学

前面写并发的时候,几乎每个函数都返回了error,然后就是if err != nil那套。这篇就把 Go 的错误处理单独拎出来说透,顺便聊聊 panic 和 recover。Go 没有 Java 那套 try-catch 异常机制,一开始我很不习惯,但写多了反而觉得这种显式的错误处理更清晰,也更容易让人思考“这个错误到底该不该在这里处理”。

Go 的 error 是什么

Go 内置的error其实就是一个接口:

typeerrorinterface{Error()string}

任何类型只要实现了Error() string方法,就实现了error接口。所以自定义错误非常简单。

errors.New 创建简单错误

最常用的创建错误方式:

import"errors"err:=errors.New("这是一个错误")fmt.Println(err.Error())// 这是一个错误

fmt.Errorf 格式化错误

err:=fmt.Errorf("读取文件 %s 失败","test.txt")fmt.Println(err)// 读取文件 test.txt 失败

fmt.Errorf内部其实就是调用errors.New,只是多了格式化功能。

错误处理的基本模式

Go 里处理错误几乎是“约定俗成”的套路:

funcdoSomething()(int,error){// 业务逻辑return0,nil}result,err:=doSomething()iferr!=nil{// 处理错误log.Println("出错了:",err)return}// 正常流程fmt.Println(result)

刚开始我觉得这种写法很啰嗦,每个可能出错的函数都要跟着一个if err != nil。但时间长了发现,这种显式处理强迫你去思考每一个错误分支,代码的健壮性反而更高。相比之下,Java 的异常机制很容易让人忽略某些异常,直到运行时才暴露。

自定义错误类型

有时候光靠errors.New不够,需要携带更多上下文,比如错误码、堆栈信息等。这时可以定义一个结构体来实现error接口:

typeMyErrorstruct{CodeintMessagestring}func(e*MyError)Error()string{returnfmt.Sprintf("code=%d, message=%s",e.Code,e.Message)}// 使用err:=&MyError{Code:404,Message:"资源不存在"}fmt.Println(err.Error())

这种方式很灵活,可以在错误里塞任何字段。很多第三方库都自定义了错误类型,比如net.OpErroros.PathError等。

错误包装:%w 和 errors.Is / errors.As

错误的包装

Go 1.13 引入了错误包装机制,用fmt.Errorf配合%w动词可以把一个错误包进另一个错误里:

funcreadFile(pathstring)error{_,err:=os.Open(path)iferr!=nil{returnfmt.Errorf("打开文件 %s 失败: %w",path,err)}returnnil}

这样返回的错误包含了原始错误err,同时增加了新的上下文信息。外层可以通过errors.Iserrors.As来检查是否包含某个特定错误。

errors.Is:判断错误链中是否包含某个错误

err:=readFile("test.txt")iferrors.Is(err,os.ErrNotExist){fmt.Println("文件不存在")}elseiferr!=nil{fmt.Println("其他错误:",err)}

errors.Is会沿着错误链一直找,看是否包含目标错误。这样即便错误被包装了好几层,也能准确判断出来。

errors.As:提取错误链中的特定类型

varmyErr*MyErroriferrors.As(err,&myErr){fmt.Println("错误码:",myErr.Code)}

errors.As会检查错误链中是否存在某个具体类型的错误,如果存在就赋值给目标变量。这在自定义错误类型时很有用。

注意%w只能包装一个错误,不能包装多个。而且最好只包装一次,不要重复包装,否则错误链会变得很啰嗦。

panic 和 recover:不是用来处理常规错误的

Go 里还有一个panic机制,可以让程序立即崩溃。但它不是用来处理普通错误的,而是用来处理不可恢复的严重问题,比如数组越界、空指针引用、除零等运行时错误。

panic 的触发

funcmain(){panic("程序崩溃了")fmt.Println("这行不会执行")}

运行时会打印 panic 信息和堆栈,然后退出。

除了手动调用panic,很多运行时错误也会触发 panic,比如:

vars[]intfmt.Println(s[0])// panic: runtime error: index out of range

recover 捕获 panic

recover是 Go 提供的唯一能从 panic 中恢复的机制。它必须在defer函数中调用才有效:

funcsafeRun(){deferfunc(){ifr:=recover();r!=nil{fmt.Println("捕获到 panic:",r)}}()panic("出事了")fmt.Println("这行不会执行")}funcmain(){safeRun()fmt.Println("程序继续执行")}

输出:

捕获到 panic: 出事了 程序继续执行

recover返回 panic 传入的值,如果没有 panic 则返回nil。需要注意的是,recover只能恢复同一个 goroutine 里的 panic,跨 goroutine 的 panic 无法恢复。

什么时候该用 panic?

原则:能返回 error 就不要用 panic。panic 意味着程序进入不可控状态,应该立即终止。一般用于:

  • 程序启动时的致命错误(如配置加载失败)
  • 编程错误(比如函数内部逻辑矛盾,应该用 panic 暴露问题)
  • 并发访问未初始化的 map(Go 运行时会 panic)

我刚开始写 Go 的时候,总想用 panic 来简化错误处理,结果被同事提醒:panic 会中断整个程序,而 error 让调用方有机会决定如何处理。所以现在除非真的无法继续运行,否则一律返回 error。

一个常见坑:recover 只在 defer 函数里有效

看这段代码:

funcwrongRecover(){ifr:=recover();r!=nil{fmt.Println("捕获到:",r)}panic("错误")}funcmain(){wrongRecover()fmt.Println("这行不会执行")}

这段代码不会捕获 panic,因为recover必须直接写在defer函数里,而且要在 panic 发生之前已经注册。正确的做法是:

funccorrectRecover(){deferfunc(){ifr:=recover();r!=nil{fmt.Println("捕获到:",r)}}()panic("错误")}

这个细节面试经常考。

另一个坑:panic 后 defer 的返回值

如果函数有命名返回值,在 panic 被 recover 后,defer 可以修改返回值,这也是前面讲 defer 时提到的。结合 panic/recover 可以写出比较高级的错误处理模式,但用不好会让代码很费解,建议少用。

小结

这篇把错误处理和 panic/recover 梳理了一遍:

  • error是接口,任何实现Error() string的类型都可以当作错误。
  • 处理错误的基本模式:返回error,调用方if err != nil检查。
  • 自定义错误类型可以携带更多信息。
  • 错误包装用%w,配合errors.Iserrors.As判断错误链。
  • panic用于不可恢复的错误,recover只能在defer函数里有效。
  • 能返回 error 就不要用 panic,这是 Go 的哲学。

下一篇准备聊聊 Go 的包管理和依赖管理,毕竟实际项目不可能只写一个文件。Go Modules 是怎么工作的,怎么管理依赖版本,还有工作区模式,这些内容面试也经常问。到时候继续记录。

如果这篇文章对你有帮助,欢迎点赞收藏,评论区一起交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 6:55:36

本地优先的 AI LaTeX 写作台:写论文不怕断网泄密

1. 引言 写论文的人,往往都经历过这样的崩溃时刻:网络断线、排版到一半的 LaTeX 工程打不开;或是在线写作工具突然抽风,辛辛苦苦写的内容进了“别人的服务器”还无法导出;更让人不安的是,尚未发表的研究数据…

作者头像 李华
网站建设 2026/8/25 6:55:13

游戏耳机选购指南:平坦频率响应与舒适佩戴如何提升FPS竞技体验

最近在逛社区时,发现一个挺有意思的现象:很多玩家,尤其是刚入坑FPS(第一人称射击)游戏的玩家,在挑选耳机时特别容易陷入一个误区——认为“听声辨位”是游戏耳机最核心、甚至唯一的功能。于是,预…

作者头像 李华
网站建设 2026/8/25 6:54:05

Python 魔术方法

Python 魔术方法(Magic Method / 双下划线方法 __xxx__)特征:前后双下划线 __名字__,又叫特殊方法。 你不要手动直接调用它,Python解释器会在特定语法、内置操作触发时自动调用。举个直观例子: obj obj2 …

作者头像 李华
网站建设 2026/8/25 6:53:08

DeepSeek Harness:从零构建可追溯的AI编程工作流

最近在探索 AI 编程工具时,发现了一个非常有意思的新项目——DeepSeek Harness。它不像传统的代码生成工具那样,给你一个黑盒结果就结束了,而是将整个 AI 交互过程拆解成一个个可插拔、可追溯的“插件”。无论是代码生成、代码审查&#xff0…

作者头像 李华
网站建设 2026/8/25 6:50:33

[063][调度模块]事件驱动的日志记录与高效查询体系

[063][调度模块]事件驱动的日志记录与高效查询体系 本文章代码: gitee , gitcode , github 1. 事件驱动架构:解耦调度与日志 任务状态变更时,ScheduleTaskManager 发布 ChangeStatusEvent,JobLogEventConsumer 异步消费(需配置…

作者头像 李华
网站建设 2026/8/25 6:48:58

2026年智能离子棒将带来哪些惊喜?解密这款科技新宠的魅力

在科技飞速发展的2026年,智能离子棒作为改善生产问题的新宠,正崭露头角。而上海净源机电科技有限公司(IONEAS)的836/JX和600JL系列离子棒,更是凭借着卓越的技术优势和出色的应用能力,为众多行业带来了新的惊…

作者头像 李华