GO 1.20 新功能:多重错误包装
2022-12-27 14:34:00

预计将于 2023 年 2 月发布的 Go 1.20 有一个小的变化,对于那些大量使用错误包装的应用程序来说,可能会有效改进它们的错误处理方法。


(资料图片仅供参考)

让我们看一下它的用法,但首先,需要简要回顾一下什么是错误包装。如果你已经掌握了可以直接跳到下面的 “Go 1.20 新功能” 部分以获取新的信息。

Go 中的错误是实现一个非常简单的接口:

typeerrorinterface{Error()string}

错误类型可以是任何东西,从string本身到int,但通常它们是struct类型。下面这个例子来自标准库:

typeerrstruct{sstring}func(e*err)Error()string{returne.s}

要检查 Go 中的错误,你只需比较一个值(在本例中为int值):

iferr==io.EOF{//...}

第二种常见的用法是检查错误的类型,那也意味着要写更多的代码:

ifnerr,ok:=err.(net.Error){//...(usenerrwhichisanet.Error)}

在上面的例子中,类型断言测试类型net.Error的err值,并创建一个新变量nerr,它可以在 if 语句中使用。Go 中的错误方便理解、易于使用且非常高效。

错误包装

从 Go 1.13 开始,引入了错误包装。包装允许将错误嵌入到其他错误中,就像在其他语言中包装异常一样。这非常实用,比如函数遇到 “record not found” 错误时,可以向错误信息中添加更多上下文信息,例如 “unknown user: record not found”。

Go 中错误包装设计背后的有趣想法是:契约不用关心错误类型、结构或它们是如何创建的。而唯一关注的是解包过程和转换为字符串,因为这两者是必须的。这就非常容易实现:支持解包的错误类型必须实现Unwrap() error方法。

标准库中没有(命名的)接口可以向您展示,因为接口是隐式实现的,没有必要单独写一个。这里我们写一个只是为了更好说明这篇文章:

typeWrappedErrorinterface{Unwrap()error}

我们来看看 Go 标准库(实际上是 package fmt)中是如何实现包装错误的:

typewrapErrorstruct{msgstringerrerror}func(e*wrapError)Error()string{returne.msg}func(e*wrapError)Unwrap()error{returne.err}

由于上面错误类型实现了Error() string方法,所以说 Go 中的错误实际上最终是字符串并没有错,因此需要一种创建这些字符串的良好机制。这就是标准库中的函数fmt.Errorf发挥作用的地方:

varRecordNotFoundErr=errors.New("notfound")constname,id="lzap",13werr:=fmt.Errorf("unknownuser%q(id%d):%w",name,id,recordNotFoundErr)fmt.Println(werr.Error())

一个特殊格式的动词%w,每次调用只能使用一次(稍后会详细介绍),用于错误参数。除此之外,该函数的工作方式类似于fmt.Printf函数。下面的例子打印了这个结果:

unknownuser"lzap"(id13):notfound

如你所见,错误包装本质上是一个链表。要解包错误,请使用errors.Unwrap函数,该函数将为链表中的最后一个错误值返回nil。要检查错误类型或值,需要遍历整个列表,这对于需要进行频繁的错误检查不太实用。幸运的是,有两个辅助函数可以做到这一点。

检查包装错误列表中的值:

iferrors.Is(err,RecordNotFoundErr){//...}

检查特定类型(下面例子是来自标准库的网络错误):

varnerr*net.Erroriferrors.As(err,&nerr){//...(usenerrwhichisa*net.Error)}

以上总结了 Go 1.13 及更高版本中的错误包装。

Go 1.20 新特性

让我们看看 Go 1.20 中真正的新功能,从函数errors.Join开始,它通过可变参数包装错误列表:

err1:=errors.New("err1")err2:=errors.New("err2")err:=errors.Join(err1,err2)fmt.Println(err)

当事先不知道错误数量时,此功能可用于将错误连接在一起。一个很好的例子是从 goroutines 收集错误。值得一提的是,该函数将列表中的错误与换行符连接起来。上面的代码片段打印:

err1err2

对于许多应用程序或(日志记录)库来说,这可能会存在问题,它们期望错误通常只是没有换行符的字符串。幸运的是,Go 1.20 中的另一个变化改变了fmt.Errorf的行为:该函数现在接受多个%w格式说明符:

err1:=errors.New("err1")err2:=errors.New("err2")err:=fmt.Errorf("%w+%w",err1,err2)fmt.Println(err)

以前会导致格式错误的格式字符串现在可以正确打印:

err1+err2

同时包装多个错误实现Unwrap() error,这是可能的吗?

事实证明,在 Go 1.20 标准库中有一种新的机制: 实现Unwrap() []error函数的错误类型可以包装多个错误。让我们来看看这是如何在库中实现的:

typejoinErrorstruct{errs[]error}func(e*joinError)Error()string{//concatenateerrorswithanewlinecharacter}func(e*joinError)Unwrap()[]error{returne.errs}

一个理论上的接口,但标准库中实际不存在,如下所示:

typeMultiWrappedErrorinterface{Unwrap()[]error}

由于 Go 不允许方法重载,因此每种类型都可以实现Unwrap() error或Unwrap() []error,但不能同时实现。还记得我提到过包装错误本质上是一个链表吗?实现前一个(新引入的)方法的类型实际上形成了一个链接树,函数errors.Is和errors.As的工作方式相同,只是现在它们需要遍历树而不是列表。根据文档,该实现执行预排序、深度优先遍历。

这确实是 Go 1.20 带来的全部,它可能看起来是一个小的变化,但它提供了如何有效和干净地处理错误的新方法。在展示真实示例之前,让我总结一下新功能:

新的Unwrap []error函数契约允许遍历错误树。

新的errors.Join函数,这是一个方便的函数,用于连接两个错误字符串值(使用换行符)。

现有函数errors.Is和errors.As已更新,可以同时处理错误列表和错误树。

现有函数fmt.Errorf现在接受多个%w格式动词。实践

上面这一切都很棒,但是你如何在实践中利用它呢?

在一个小型 REST API 微服务中,我们通过errors.New和fmt.Errorf处理来自 DAO 包(数据库)、REST 客户端(其他后端服务)和其他包的各种错误。返回的 HTTP 状态代码应该是 2xx、4xx 或 5xx,具体取决于错误状态以遵循最佳 REST API 实践。实现此过程的一种方法是解开主 HTTP 处理程序中的错误并找出它是哪种错误。

然而,通过多重错误包装,现在可以包装根本原因(例如数据库返回 “no records found” )和返回给用户 HTTP 代码(在本例中为 404)。

一个工作示例如下所示:

packagemainimport("errors""fmt")//commonHTTPstatuscodesvarNotFoundHTTPCode=errors.New("404")varUnauthorizedHTTPCode=errors.New("401")//databaseerrorsvarRecordNotFoundErr=errors.New("DB:recordnotfound")varAffectedRecordsMismatchErr=errors.New("DB:affectedrecordsmismatch")//HTTPclienterrorsvarResourceNotFoundErr=errors.New("HTTPclient:resourcenotfound")varResourceUnauthorizedErr=errors.New("HTTPclient:unauthorized")//applicationerrors(thenewfeature)varUserNotFoundErr=fmt.Errorf("usernotfound:%w(%w)",RecordNotFoundErr,NotFoundHTTPCode)varOtherResourceUnauthorizedErr=fmt.Errorf("unauthorizedcall:%w(%w)",ResourceUnauthorizedErr,UnauthorizedHTTPCode)funchandleError(errerror){iferrors.Is(err,NotFoundHTTPCode){fmt.Println("Willreturn404")}elseiferrors.Is(err,UnauthorizedHTTPCode){fmt.Println("Willreturn401")}else{fmt.Println("Willreturn500")}fmt.Println(err.Error())}funcmain(){handleError(UserNotFoundErr)handleError(OtherResourceUnauthorizedErr)}

这将打印:

Willreturn404usernotfound:DB:recordnotfound(404)Willreturn401unauthorizedtocallotherservice:HTTPclient:unauthorized(401)

从这样的人工代码片段中可能看起来不太明显的是,实际上的错误声明通常分布在许多包中,并且不容易跟踪所有可能的错误以确保所需的 HTTP 状态代码。在这种方法中,所有在一个地方声明的应用程序级包装错误也包含了 HTTP 代码。

请注意,这在 Go 1.19 或更早版本中是不可能的,因为fmt.Errorf函数只会包装第一个错误。该代码确实在 1.19 上可以编译,甚至不会产生运行时恐慌,但它实际上不会工作。

显然,常见的 HTTP 状态代码很容易成为一种新的错误类型(基于int类型),因此可以通过errors.As轻松提取实际代码,但我想让示例保持简单。

Feel free to play around with the code on Go Playground. Make sure to use “dev branch” or 1.20+ version of Go. 可以在 Go Playground 上自由运行上述代码。确保使用 “dev branch” 或 Go 的 1.20+ 版本。现有应用

在你的应用程序中实施新功能时,请注意errors.Unwrap函数。对于具有Unwrap() []error的错误类型,它总是返回nil:

err1:=errors.New("err1")err2:=errors.New("err2")err:=errors.Join(err1,err2)unwrapped:=errors.Unwrap(err)fmt.Println(unwrapped)

由于 Go 1.X 兼容性承诺,这会打印出 “nil”。当你引入多个包装错误时,请确保检查展开代码。幸运的是,典型 Go 代码中的大部分错误检查都是使用errors.Is和errors.As完成的。

错误包装并不是 Go 中所有错误处理的最终解决方案。它只是提供了一种干净的方法来处理典型 Go 应用程序中的错误,对于简单应用程序来说也许就完全足够了。原文地址:https://lukas.zapletalovi.com/posts/2022/wrapping-multiple-errors/原文作者:Lukáš Zapletal本文永久链接:https://github.com/gocn/translator/blob/master/2022/w50_Wrapping_multiple_errors译者:haoheipi

校对:watermelo

往期推荐

谷歌发布查找开源漏洞的Go工具OSV-Scanner

最好的Go框架:没有框架?

「每周译Go」如何在Go中构造For 循环

想要了解Go更多内容,欢迎扫描下方关注公众号,回复关键词 [实战群],就有机会进群和我们进行交流

分享、在看与点赞Go

GO 1.20 新功能:多重错误包装

2022-12-27 14:34:00

当前热门:反洗钱金融观察:近三月37家银行涉反洗钱违规 农商行领罚占比加大

2022-12-27 08:57:01

资讯:立陶宛采购美制“弹簧刀”600反装甲无人机

2022-12-26 20:20:18

全球今日讯!羊的前腿有嘎啦哈吗?

2022-12-26 15:26:09

微粒贷网贷逾期35年还不起影响征信吗 精彩看点

2022-12-26 09:56:39

支持开票 | Python实证指标构建与文本分析 热点聚焦

2022-12-25 19:25:38

影响你的钱袋子!除了年终奖 年底这几笔钱别忘领|焦点热门

2022-12-25 01:51:08

士兰微(600460)12月23日主力资金净卖出6652.82万元

2022-12-24 07:52:00

2023年1月1日起唐山市将再次提高“残疾人两项补贴”标准

2022-12-23 17:55:35

环球速讯:小麻袋网贷多少额度会上征信

2022-12-23 12:56:23

世界最资讯丨四大专委会 开辟消费维权新路径

2022-12-23 07:38:03

环球印务: 第五届监事会第十七次会议决议公告

2022-12-22 18:25:41

世界最资讯丨黄骅法院一案件庭审 获省“优秀庭审”

2022-12-22 12:44:33

力争粮食产能再上新台阶

2022-12-22 05:35:07

九喜股份12月21日挂牌新三板:曾参与制定多项湖南省地方标准去年净利润约819万元|环球今日报

2022-12-21 16:56:43

医渡科技中期收入和毛利率齐下滑 现金需求主要靠融资来满足|环球快看

2022-12-21 10:33:44

环球观焦点:12月20日基金净值:长城品质成长混合A最新净值0.7467,跌1.49%

2022-12-21 01:02:11

天纺标(871753)12月20日游资资金净卖出15.99万元-世界关注

2022-12-20 15:58:28

世界杯丢冠加速转会?曝姆巴佩回国后宣布离队!1.5亿引4豪门竞购

2022-12-20 10:56:52

最新资讯:犀牛看市1219:大盘全天震荡调整 养老概念迎利好

2022-12-19 21:25:51

智新电子(837212)12月19日游资资金净卖出15.51万元 世界要闻

2022-12-19 15:31:04

养老服务成为扩内需重要抓手 养老概念股开盘活跃

2022-12-19 09:35:04

建安区退役军人事务局做好病退军人移交安置工作

2022-12-19 00:06:08

马刺八人上双痛宰火箭 穆雷19+5+10波特16+5+9

2022-12-18 11:21:47

世界热文:广州公证处出具首份诉前调解区块链赋强公证书

2022-12-17 12:25:29

二手水车交易市场,58同城二手洒水车转让-世界速看

2022-12-16 21:54:16

全球即时看!本周盘点(12.12-12.16):中恒集团周涨0.34%,主力资金合计净流出3355.19万元

2022-12-16 15:39:43

华夏航空(002928)12月15日主力资金净卖出1473.53万元

2022-12-16 09:02:29

扩建项目遭搁浅 郏县联社资金扶持获称赞

2022-12-15 15:02:04

鹏华兴安定期开放混合基金经理变动:增聘牛孟艺为基金经理

2022-12-15 09:12:28

【报资讯】苹果6价格多少钱一台(苹果10价格多少钱一台)

2022-12-14 19:18:28

火星人:赎回理财本金1.5亿元及收益100.97万元 时讯

2022-12-14 14:35:54

五洲特纸(605007):特种纸领军企业 横纵扩张打开成长空间

2022-12-14 07:32:24

网贷逾期13年有哪些严重后果|环球热点

2022-12-13 16:02:18

福龙马董秘回复:我们公司目前未涉及医疗废物处理的相关业务

2022-12-13 10:25:15

电声股份(300805.SZ):控股股东质押的555.7万股延期购回

2022-12-12 20:23:00

广发期货:铁矿石1-5反套止盈,短多05合约关注850阻力位|环球今热点

2022-12-12 14:46:04

全球关注:全国多地天气干燥,静电指数地图出炉

2022-12-10 16:59:04

养元饮品(603156)12月8日主力资金净买入1846.91万元

2022-12-09 08:02:08

当前报道:12月7日基金净值:华夏新锦顺混合A最新净值1.0805,跌0.64%

2022-12-08 02:34:54

股票行情快报:爱威科技(688067)12月6日主力资金净买入105.56万元|环球速看料

2022-12-06 18:52:15

年内获公募调研A股数量同比增长 基金公司调研频次超7万次

2022-08-08 10:43:07

白酒新国标实施一个月 不少酒企纷纷推出适应新国标的光瓶酒新品

2022-07-08 16:18:41

清洁家电成为市场热点 目前正处于高集中度与高毛利的发展初期

2022-07-08 16:18:11

《阿凡达2:水之道》将于12月16日北美公映 电影片长约为三小时

2022-07-08 16:17:11

珠海洪鹤大桥、鹤港高速明起正式收费 明年1月8日前ETC车辆免通行费

2022-07-08 16:16:44

经营证券期货业务许可证获批 安徽金融业七大类牌照齐全

2022-03-20 14:38:08

多措并举全面排查 咸阳全力做好市第八次党代会安全保障工作

2022-03-20 14:30:43

便民办税:灵源镇大王村为失联多年的特殊村民办理医保

2022-03-20 14:28:01

践行新使命忠诚保大庆:八旬老人“丢失”钱财 彬州民警帮找回

2022-03-20 14:26:38

创新集聚新动能 宁国着力构建汽车零部件产业龙头

2022-03-20 14:22:29

碑林区司法局:护航消费普法先行

2022-03-19 15:55:13

桑树坪矿“四项举措”筑牢矿井环保防线

2022-03-19 15:40:54

两岸专家:美日澳频打“台湾牌”加剧台海局势紧张

2022-01-11 10:33:28

人民网评:金色的盾牌,无悔的坚守

2022-01-11 10:33:28

数字政通:步入无人驾驶新赛道 与主线科技签署战略合作

2022-01-11 10:33:27

珠海高新区新政揽才 最高600万元住房补

2022-01-11 10:33:27

台胞在西安:“抗击疫情需要我们每一个人的力量”

2022-01-11 10:33:26

在京台生体验冰雪乐趣

2022-01-11 10:33:26

澳门未来发展有了新的时间表

2022-01-11 10:33:24

最高检:法治副校长应指导学校落实未成年人保护责任

2022-01-11 10:33:24

检察官担任法治副校长有了“指挥棒”

2022-01-11 10:33:22

全国首份《家庭教育令》来了!督促家长“依法带娃”

2022-01-11 10:33:22

俄军装甲车辆将具备隐身能力

2022-01-11 10:33:21

俄美双边对话 欧盟处境尴尬

2022-01-11 10:33:21

国资委:决战决胜国企改革三年行动 更好发挥国有经济战

2022-01-11 10:33:19

美海岸警卫队盯上濒海战斗舰

2022-01-11 10:33:19

证监会发布标准:统一金融行业对移动互联网应用程序的安

2022-01-11 10:33:17

日本声称以电磁炮加强防御

2022-01-11 10:33:17

2022-01-29 14:39:05

江苏南京:上万块显示屏支撑征兵宣传

2022-01-11 10:33:16

2022-01-29 14:39:05

广东省汕头市组织军地海上联合搜救演练

2022-01-11 10:33:14

2022-01-29 14:37:35

山东省枣庄军分区组织两级首长机关野营拉练

2022-01-11 10:33:08

2022-01-29 14:37:35

83年后,“故乡土”撒在烈士墓前

2022-01-11 10:33:06

岛与岸的守望:你的岛,我的岸

2022-01-11 10:33:04

2022-01-29 14:37:35

长武县税务局:聚焦企业需求 精准精细服务

2022-01-11 10:33:02

集安组织将就哈局势举行视频峰会

2022-01-11 10:33:02

美俄新一轮战略稳定对话前景不容乐观

2022-01-11 10:33:01

质效双优赋能奋进 咸阳市城管执法局召开项目推进会

2022-01-11 10:33:01

第二轮第五批中央生态环境保护督察全面完成督察进驻工作

2022-01-11 10:33:00

小小菜花架起保供连心桥——汉中桃心岛商贸公司在行动

2022-01-11 10:33:00

江秋莲诉刘暖曦生命权纠纷案一审宣判

2022-01-11 10:32:58

2022-01-29 14:37:21

新疆军区某团侦察连官兵边关巡逻

2022-01-11 10:32:54

第78集团军某旅实战化演练设强对手设难情况

2022-01-11 10:32:54

军队医疗待遇保障新规定如期落地有序施行

2022-01-11 10:32:52

2022-01-29 14:37:21

2022-01-29 14:37:21

荣盛发展:2021年签约金额1345.58亿元 同比增长5.87%

2022-01-11 10:32:50

2022-01-29 14:37:09

2022-01-29 14:37:09

证监会发布《关于北京证券交易所上市公司转板的指导意见

2022-01-11 10:32:47

2022-01-29 14:37:09

2022-01-29 14:37:09

奥密克戎高速传播 美国医疗物资供应无法满足病毒检测需求

2022-01-11 10:32:44

原住民控诉美政府在其家园进行“900次核试验”:这是种族

2022-01-11 10:32:42