Go error 处理:errors.Is/As 与错误包装
📅 2026/7/10 22:23:03
👁️ 次浏览
Go error 处理:errors.Is/As 与错误包装Go 的错误处理一直被吐槽啰嗦,但真正让人头疼的不是if err ! nil写得多,而是错误传到上层就失真了。你在最底层返回了sql.ErrNoRows,一路return err传上来,到 handler 里想判断是不是记录不存在,结果发现要么信息丢了,要么只能用err.Error() ...这种字符串比较——脆得一改文案就崩。Go 1.13 引入的错误包装(wrapping)和errors.Is/errors.As就是来解决这个的。这篇讲清楚:怎么包装错误既保留上下文又不丢原始类型,以及怎么在上层正确地判断和提取错误。朴素写法的两个坑先看没有包装时的常见做法。坑一:直接return err,上层拿到错误但不知道它是在哪一步出的。funcloadUser(idint)(*User,error){row:db.QueryRow(SELECT ... WHERE id ?,id)varu Useriferr:row.Scan(u.Name);err!nil{returnnil,err// 只返回底层错误,丢了loadUser 这一层的上下文}returnu,nil}坑二:为了加上下文,用fmt.Errorf拼字符串,结果原始错误类型被抹掉了:iferr:row.Scan(u.Name);err!nil{// %v 只是把错误转成字符串塞进去,sql.ErrNoRows 这个身份没了returnnil,fmt.Errorf(loadUser id%d: %v,id,err)}上层想判断是不是没查到记录,只能被迫写:ifstrings.Contains(err.Error(),no rows){// 脆弱:依赖错误文案,库一改就废// ...}字符串匹配是错误处理里最该避免的反模式。用 %w 包装:既加上下文又保留原始错误fmt.Errorf的%w动词(w wrap)是关键。它在字符串里嵌入上下文的同时,把原始错误链接进新错误里,原始类型不丢:funcloadUser(idint)(*User,error){row:db.QueryRow(SELECT name FROM users WHERE id ?,id)varu Useriferr:row.Scan(u.Name);err!nil{// 用 %w 而不是 %v:错误链里仍能找到 sql.ErrNoRowsreturnnil,fmt.Errorf(loadUser id%d: %w,id,err)}returnu,nil}%v和%w打印出来的文字一模一样,区别全在能不能被 errors.Is/As 识别。记住一条规则:给上层留了判断需要的错误,就用%w;纯粹只想记个日志、不需要上层再识别,用%v。errors.Is:判断是不是某个特定错误包装之后,上层用errors.Is沿着错误链查找,不管中间包了多少层:u,err:loadUser(42)iferr!nil{// errors.Is 会顺着 %w 链一路找,底层的 sql.ErrNoRows 照样能命中iferrors.Is(err,sql.ErrNoRows){http.Error(w,用户不存在,http.StatusNotFound)return}// 其他错误当 500http.Error(w,服务器错误,http.StatusInternalServerError)return}errors.Is(err, target)的语义是err 的错误链里,是否存在等于 target 的错误。这就是它取代err sql.ErrNoRows的原因——直接在错误被包装后会失败,errors.Is不会。自定义哨兵错误(sentinel error)也用同一套:// 用 errors.New 定义包级别的哨兵错误,导出给调用方判断varErrUserBannederrors.New(user is banned)funccheckUser(u*User)error{ifu.Banned{returnfmt.Errorf(checkUser %s: %w,u.Name,ErrUserBanned)}returnnil}// 调用方iferrors.Is(err,ErrUserBanned){// 精确处理被封禁这一种情况}errors.As:提取某种类型的错误拿它的字段有时候光知道是哪种错误不够,你还想拿到错误里携带的数据(比如出错的字段名、HTTP 状态码)。这时用errors.As,它把错误链里匹配类型的那个错误取出来赋值给你的变量:// 一个携带额外信息的自定义错误类型typeValidationErrorstruct{FieldstringMsgstring}func(e*ValidationError)Error()string{returnfmt.Sprintf(字段 %s 校验失败: %s,e.Field,e.Msg)}funcvalidate(u*User)error{ifu.Age0{returnValidationError{Field:age,Msg:不能为负数}}returnnil}上层用errors.As把它捞出来,读它的字段:err:validate(u)varvErr*ValidationError// errors.As 找到链里第一个 *ValidationError,赋值给 vErr,返回 trueiferrors.As(err,vErr){// 现在能拿到具体是哪个字段错了fmt.Printf(请检查字段: %s (%s)\n,vErr.Field,vErr.Msg)}注意errors.As的第二个参数是指向目标变量的指针(这里vErr本身是*ValidationError,所以传vErr)。这是新手最容易搞错的地方,传错编译期就会 panic 提示你。Is 还是 As?一句话区分errors.Is:判断是不是这一个/这一种已知错误,拿来做分支。目标是一个具体的错误值(哨兵错误)。errors.As:判断链里有没有某种类型的错误,并且要取出它携带的字段。目标是一个类型。简记:只要判断身份用 Is,还想读数据用 As。一个容易忽略的细节:别过度包装包装很好用,但不是每层都要包。原则是:每一层加的上下文要有信息增量。下面这种层层复读是噪声:// 反面例子:每层都包一遍,错误信息变成 a: b: c: d: 真正的错误returnfmt.Errorf(service call: %w,err)// 上层returnfmt.Errorf(repo query: %w,err)// 中层returnfmt.Errorf(db exec: %w,err)// 底层实践建议:在跨越模块边界或**能补充关键上下文(如 id、操作名)**时包装一次,中间纯透传的地方直接return err即可。小结fmt.Errorf用%w包装错误:既加上下文,又让原始错误留在链里可被识别;%v会抹掉类型,只适合记日志。errors.Is(err, target)沿错误链判断是不是某个特定错误,取代脆弱的和字符串匹配。errors.As(err, target)从链里提取某种类型的错误,拿来读它携带的字段;第二个参数要传指针。判断身份用Is,要读数据用As;包装要有信息增量,别层层复读。一句话记忆:用%w包上下文,用Is/As拆链判断——错误传到天边,身份也不会丢。
最近整理车载U盘,发现之前在酷狗下载的一堆歌都放不出来了,一看后缀全是kgg、kgma格式。相信很多朋友也遇到过这个坑,明明花钱开了会员,想把酷狗下载的kgg、kgma格式转换为mp3拿到车里听,结果播放器直接显示“格式不支…
📅 2026/7/10 22:23:03
1. 认识A3910与PIC18F2585这对黄金搭档 在嵌入式控制领域,电机驱动与微控制器的组合就像咖啡与牛奶的完美融合。A3910作为一款高性能全桥电机驱动芯片,配合PIC18F2585这颗工业级微控制器,能够构建出稳定可靠的智能控制系统。这套组合特别适合…
📅 2026/7/10 22:22:03
pip 22.0 多平台配置源实战指南:7大国内镜像深度评测刚接触Python开发时,最令人崩溃的瞬间莫过于看着pip安装进度条像蜗牛一样缓慢爬行。记得第一次在Windows上安装TensorFlow时,我盯着那个以KB/s为单位跳动的下载速度,一度怀疑自…
📅 2026/7/10 22:22:03
目录 一、环境信息
二、说点什么
三、老版本恢复步骤
四、实验步骤
1、测试数据生成刷检查点
2、测试数据生成不刷检查点
3、强杀服务
4、模拟redo损坏
5、启动数据库
6、查看初始化信息
7、初始化新库
8、查看关键信息
9、备份损坏redo
10、拷贝新redo到故障库
…
📅 2026/7/10 23:31:21
引言:制造业生产现场管理的核心价值在竞争日益激烈的全球制造业环境中,生产现场的管理水平直接决定了企业的生产效率、产品质量和成本控制能力。对制造业生产现场的各要素进行合理有效的计划、组织、协调、控制和检测,不仅是提升运营效率的关…
📅 2026/7/10 23:31:21
文章目录1. 概述2. 无状态应用3. 工作原理3.1 首次创建3.2 滚动更新发布3.3 故障自愈流程3.4 水平扩缩容3.5 版本回滚4. 运行状态4.1 发布中 Progressing4.2 发布完成 Complete4.3 发布失败 Failed5. 资源清单编写规范5.1 通用顶层基础规范5.2 更新策略5.2.1 Recreate 重建更新…
📅 2026/7/10 23:31:21
Python 实现基于 TCP 的聊天程序(Socket threading Tkinter 4 节点并行) 本文配套实验:TCP/IP 协议基础、Python 网络编程、Socket 编程、threading 多线程、Tkinter 图形界面。在 华为云 FlexusX 4 节点 上完成分布式聊天实测。 完整源码…
📅 2026/7/10 23:31:21
XUnity.AutoTranslator终极指南:如何快速修复游戏翻译失效问题 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator
XUnity.AutoTranslator是一款强大的Unity游戏自动翻译插件,能够为多…
📅 2026/7/10 23:31:21
揭秘音频可视化:用Sonic Visualiser开启音乐探索之旅 🎧 【免费下载链接】sonic-visualiser Visualisation, analysis, and annotation of music audio recordings 项目地址: https://gitcode.com/gh_mirrors/so/sonic-visualiser
你是否曾想过&q…
📅 2026/7/10 23:30:20
一、为什么接口自动化测试,适合用AI赋能?
大家可自行先思考一个问题:
AI赋能测试全流程,为什么优先推荐从接口自动化切入?
有三个典型原因:
接口输入结构化,AI最擅长"吃"
接口有OpenA…
📅 2026/7/10 0:00:53
终极原神FPS解锁器完整指南:轻松突破60帧限制 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock
原神FPS解锁器是一款专为《原神》玩家设计的开源工具,通过先进的Wri…
📅 2026/7/10 0:00:53
YesPlayMusic:如何用高颜值播放器重塑你的网易云音乐体验?🎵 【免费下载链接】YesPlayMusic 高颜值的第三方网易云播放器,支持 Windows / macOS / Linux :electron: 项目地址: https://gitcode.com/gh_mirrors/ye/YesPlayMusic…
📅 2026/7/10 0:00:53
1. 项目背景与核心需求 在嵌入式系统开发中,快速精确的数据检索是一个常见但极具挑战性的需求。特别是在工业控制、医疗设备和物联网终端等场景下,系统往往需要在毫秒级时间内完成关键参数的读取和写入操作。传统基于Flash存储的方案存在擦写次数有限、操…
📅 2026/7/10 22:46:54
1. 工业电流环信号传输的基础认知在工业自动化领域,4-20mA电流环传输技术已经持续服役超过半个世纪。这种看似简单的信号传输方式之所以能经久不衰,核心在于其独特的抗干扰能力——电流信号在长距离传输时几乎不受线路电阻和电压波动的影响。我曾在化工厂…
📅 2026/7/10 22:46:54
最近在项目里尝试用 YOLO 做目标检测,从环境搭建到模型训练,再到推理部署,整个过程踩了不少坑。网上的资料虽然多,但要么版本老旧,要么步骤零散不成体系,对于刚入门的新手来说,很容易卡在某个环…
📅 2026/7/10 22:46:54
目录
第一步:选对模板,省心一半
第二步:打开扫码点餐功能
开启功能按钮
桌台管理与桌码生成
第三步:个性化设计,打造品牌感
调整点餐页面
设置点餐规则 你还在让顾客站着排队点餐吗?2025年ÿ…
📅 2026/7/10 22:46:54
在业务中快速构建一个能理解私有文档、准确回答专业问题的智能助手,是很多开发团队面临的共同挑战。传统方案往往需要从零开始搭建复杂的 RAG(检索增强生成)系统,涉及文档解析、向量化、检索、大模型调用等多个环节,整…
📅 2026/7/10 22:46:54
FAE放射组学分析工具:医学影像特征探索的完整解决方案 【免费下载链接】FAE FeAture Explorer 项目地址: https://gitcode.com/gh_mirrors/fae/FAE
你是否曾经面对海量医学影像数据感到无从下手?想要从CT、MRI等影像中提取有价值的定量特征&#…
📅 2026/7/10 22:46:54