news 2026/7/10 22:22:49

Go error 处理:errors.Is/As 与错误包装

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go error 处理:errors.Is/As 与错误包装

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 这一层"的上下文}return&u,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)}return&u,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 定义包级别的哨兵错误,导出给调用方判断varErrUserBanned=errors.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.Age<0{return&ValidationError{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拆链判断——错误传到天边,身份也不会丢。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/10 22:17:46

2. 文字处理

2. 文字处理文字处理概述文档编辑新建文档打开文档保存文档文件打印与打印预览文档关闭与文档保护小结界面认识上半区域标题栏、窗口控制按钮、快速访问工具栏功能选项卡和组中间区域下半区域小结文字排版与应用文字编辑文本的录入文本的选择文本的复制与移动文本的删除文本的撤…

作者头像 李华
网站建设 2026/7/10 22:16:53

Linux diffstat 命令详解

1. 命令简介diffstat 是一个用于分析和可视化 diff 命令输出结果的统计工具。它读取 diff 命令生成的补丁文件或标准输入&#xff0c;并生成一个简洁的柱状图或统计摘要&#xff0c;用以展示文件变更的规模、类型&#xff08;增加 / 删除&#xff09;和分布情况。该工具常用于代…

作者头像 李华
网站建设 2026/7/10 22:14:13

Oracle

1. windows 安装oracle 环境要求 操作系统&#xff1a;必须是 64 位的。推荐 Windows 10/11 专业版/企业版 或 Windows Server 2019/2022 。家庭版可能会有兼容性问题。 内存与硬盘&#xff1a;至少 2GB 内存 &#xff08;强烈建议 4GB 以上&#xff09;。您的C盘或安装盘需要…

作者头像 李华
网站建设 2026/7/10 22:13:52

3分钟搞定B站缓存视频转换:m4s-converter完全指南

3分钟搞定B站缓存视频转换&#xff1a;m4s-converter完全指南 【免费下载链接】m4s-converter 一个跨平台小工具&#xff0c;将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾在B站缓存了珍贵的教学视频…

作者头像 李华