游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

深入剖析一套在Go中传递返回暴露错误,便于回查的解决方案

  作者:andruzhang,腾讯IEG后台开发工程师
  在使用Go开发的后台服务中,对于错误处理,一直以来都有多种不同的方案,本文探讨并提出一种从服务内到服务外的错误传递、返回和回溯的完整方案,还请读者们一起讨论。问题提出
  在后台开发中,针对错误处理,有三个维度的问题需要解决:函数内部的错误处理:这是一个函数在执行过程中遇到各种错误时的错误处理。这是一个语言级的问题函数模块的错误信息返回:一个函数在操作错误之后,要怎么将这个错误信息优雅地返回,方便调用方(也要优雅地)处理。这也是一个语言级的问题服务系统的错误信息返回:微服务系统在处理失败时,如何返回一个友好的错误信息,依然是需要让调用方优雅地理解和处理。这是一个服务级的问题,适用于任何语言函数内部的错误处理
  一个面向过程的函数,在不同的处理过程中需要handle不同的错误信息;一个面向对象的函数,针对一个操作所返回的不同类型的错误,有可能需要进行不同的处理。此外,在遇到错误时,也可以使用断言的方式,快速中止函数流程,大大提高代码的可读性。
  在许多高级语言中都提供了try。。。catch的语法,函数内部可以通过这种方案,实现一个统一的错误处理逻辑。而即便是C这种中级语言虽然没有,但是程序员也可以使用宏定义的方式,来实现某种程度上的错误断言。
  但是,对于Go的情况就比较尴尬了。Go的错误断言
  我们先来看断言,我们的目的是,仅使用一行代码就能够检查错误并终止当前函数。由于没有throw,没有宏,如果要实现一行断言,有两种方法。
  第一种是把if的错误判断写在一行内,比如:iferr!nil{returnerr}
  第二种方法是借用panic函数,结合recover来实现:funcSomeProcess()(errerror)deferfunc(){ife:recover();e!nil{erre。(error)}}()assert:func(condbool,fstring,a。。。interface{}){if!cond{panic(fmt。Errorf(f,a。。。))}}。。。errDoSomething()assert(errnil,DoSomething()error:w,err)。。。}
  这两种方法都值得商榷。
  首先,将if写在同一行内的问题有:这种写法,虽然理论上符合Go代码规范,但是在实操中,花括号不换行这一点还是有点争议的,笔者在实际代码中也很少见到过不够直观,而且在花括号中也不方便写其他语句,原因是Go的规范中强烈不建议使用;来分隔代码语句(if判断除外)
  至于第二种方法,我们要分情况看;首先panic的设计原意,是在当程序或协程遇到严重错误,完全无法继续运行下去的时候,才会调用(比如段错误、共享资源竞争错误)。这相当于Linux中FATAL级别的错误日志。仅仅用来进行普通的错误处理(ERROR级别),杀鸡用牛刀了。panic调用本身,相比于普通的业务逻辑,的系统开销是比较大的。而错误处理这种事情,可能是常态化逻辑,频繁的panicrecover操作,也会大大降低系统的吞吐。
  不过使用panic来断言的方案,虽然在业务逻辑中基本上不用,但在测试场景下则是非常常见的。测试嘛,用牛刀有何不可?稍微大一点的系统开销也没啥问题。对于Go来说,非常热门的单元测试框架goconvey就是使用panic机制来实现单元测试中的断言,用的人都说好。
  综上,在Go中,对于业务代码,笔者不建议采用断言,遇到错误的时候建议还是老老实实采用这种格式:iferr:DoSomething();err!nil{。。。}
  而在单测代码中,则完全可以大大方方地采用类似于goconvey之类基于panic机制的断言。Go的try。。。catch
  众所周知Go是没有try。。。catch的,而且从官方的态度来看,短时间内也没有考虑的计划。但程序员有这个需求呀。笔者采用的方法,是将需要返回的err变量在函数内部全局化,然后结合defer统一处理:funcSomeProcess()(errerror){注意,err变量必须在这里有定义deferfunc(){iferrnil{return}这下面的逻辑,就当作catch作用了iferrors。Is(err,somepkg。ErrRecordNotExist){errnil这里是举一个例子,有可能捕获到某些错误,对于该函数而言不算错误,因此errnil}elseiferrors。Like(err,somepkg。ErrConnectionClosed){。。。或者是说遇到连接断开的操作时,可能需要做一些重连操作之类的;甚至乎还可以在这里重连成功之后,重新拉起一次请求}else{。。。}}()。。。iferrDoSomething();err!nil{return}。。。}
  这种方案要特别注意变量作用域问题。比如前面的iferrDoSomething();err!nil{行,如果我们将err。。。改为err:。。。,那么这一行中的err变量和函数最前面定义的(errerror)不是同一个变量,因此即便在此处发生了错误,但是在defer函数中无法捕获到err变量了。
  在try。。。catch方面,笔者其实没有特别好的方法来模拟,即便是上面的方法也有一个很让人头疼的问题:defer写法导致错误处理前置,而正常逻辑后置了,从可读性的角度来说非常不友好。因此也希望读者能够指教。同时还是希望Go官方能够继续迭代,支持这种语法。函数模块的错误信息返回
  这一点在Go里面,一开始看起来还是比较统一的,这就是Go最开始就定义的error类型,以系统标准的方式,统一了进程内函数级的错误返回模式。调用方使用iferr!nil的统一模式,来判断一个调用是不是成功了。
  但是随着Go的逐步推广,由于error接口的高自由度,程序员们对于如何判断该错误是什么错误的时候,出现了分歧。Go1。13之前
  在Go1。13之前,对于error类型的传递,有三种常见的模式:流派
  这个流派很简单,就是将各种错误信息直接定义为一个类枚举值的模式,比如:var(ErrRecordNotExisterrors。New(recordnotexist)ErrConnectionClosederrors。New(connectionclosed)。。。)
  当遇到相应的错误信息时,直接返回对应的error类枚举值就行了。对于调用方也非常方便,可以采用switchcase来判断错误类型:switcherr{casenil:。。。caseErrRecordNotExist:。。。default:。。。}
  个人觉得这种设计模式本质上还是Cerrorcode模式。类型断言流派
  这种流派则是充分使用了error是一个interface的特性,重新自定义一个error类型。一方面是用不同的类型来表示不同的错误分类,另一方面则能够实现对于同一错误类型,能够给调用方提供更佳详尽的信息。举个例子,我们可以定义多个不同的错误类型如下:typeErrRecordNotExisterrImpltypeErrPermissionDeninederrImpltypeErrOperationTimeouterrImpltypeerrImplstruct{msgstring}func(eerrImpl)Error()string{returne。msg}
  对于调用方,则通过以下代码来判断不同的错误:iferrnil{OK}elseif,ok:err。(ErrRecordNotExist);ok{处理记录不存在的错误}elseif,ok:err。(ErrPermissionDenined);ok{处理权限错误}else{处理其他类型的错误}fmt。Errorf流派iferr:DoSomething();err!nil{returnfmt。Errorf(DoSomething()error:v,err)}
  这种模式,一方面可以透传底层错误,另一方面又可以添加自定义的信息。但对于调用方而言,灾难在于如果要判断某一个错误的具体类型,只能用strings。Contains()来实现,而错误的具体描述文字是不可靠的,同一类型的信息可能会有不同的表达;而在fmt。Errorf的过程中,各个业务添加的额外信息也可能会有不同的文字,这带来了极大的不可靠性,提高了模块之间的耦合度。Go1。13之后
  在go1。13版本发布之后,针对fmt。Errorf增加了wraping功能,并在errors包中添加了Is()和As()函数。关于这个模式的原理和使用已经有很多文章了,本文就不再赘述。
  这个功能,合并并改造了前文的所谓流派和fmt。Errorf流派,统一使用errors。Is()函数;此外,也算是官方对类型断言流派的认可(专门用As()函数来支持)。
  在实际应用中,函数模块透传错误时,应该采用Go的errorwrapping模式,也就是fmt。Errorf()配合w使用,业务方可以放心地添加自己的错误信息,只要调用方统一采用errors。Is()和errors。As()即可。服务系统的错误信息返回传统方案
  服务系统层面的错误信息返回,大部分协议都可以看成是codemessage模式或者是其变体:code是数字或者预定义的字符串,可以视为整型或者是字符串类型的枚举值如果是数字的话,大部分情况下是使用0表示成功,小部分则采用一个比较规整的十进制数字表示成功,比如1000、10000等如果是预定义的字符串,那么是使用success、OK等字符串表示成功,或者是直接以空字符串、甚至是不返回字符串字段来表示成功
  message字段则是错误信息的具体描述,大部分情况下都是一个人类可读的句子一般而言,只有当code表示错误的时候,这个message字段才有返回的必要。
  这种模式的特点是:code是给程序代码使用的,代码判断这是一个什么类型的错误,进入相应的分支处理;而message是给人看的,程序可以以某种形式抛出或者记录这个错误信息,供用户查看。存在问题
  在这一层面有什么问题呢?codeforcomputer,messageforuser,好像挺好的。
  但有时候,我们可能会收到用户客户反馈一个问题:XXX报错了,帮忙看看什么问题?。用户看不懂我们的错误提示吗?
  在笔者的经验中,我们在使用codemessage机制的时候,特别是业务初期,难以避免的是前后端的设计文案没能完整地覆盖所有的错误用例,或者是错误极其罕见。因此当出现错误时,提示暧昧不清(甚至是直接提示错误信息),导致用户从错误信息中找到解决方案
  在这种情况下,尽量覆盖所有错误路径肯定是最完美的方法。不过在做到这一点之前,码农们往往有下面的解决方案:遇到未定义错误时,后端在code中返回一个统一的错误码,并且将详细的错误信息记录在message中。不过这个模式有下面的问题:客户端提示此类信息时,如果将message信息直接展示,可能会展示很多让用户看不懂(也没必要看懂)的文字,而且文字可能会很长(万一是一个panic信息),这对用户来说非常不友好如果开发者不注意,message信息可能会暴露程序细节,比如连接DB失败的信息里可能会涉及数据库的用户名、IP。敏感信息一旦暴露,轻则安全教育,重则高压线伺候还是类似上面的方法,返回统一的错误码,message则直接用一个通用的unknownerror或未知错误,请联系XXX之类的提示信息。但是这个时候,我们要怎么查错呢?如果主调方是另一个模块的话还好,用户肯定是个程序员,这个时候只要对对方提供requestIDtrackID过来就行了。如果对方是个普通用户,难道让用户F12看控制台吗?(别笑,我们还真让用户这么干过)如果是移动端,那可一点看的机会都没;如果将traceID暴露给用户,那么长的ID,谁记得住啊。
  既要隐藏信息,又要暴露信息,我可以摔盘子吗解决方案
  这里,笔者从日益普及的短信验证码有了个灵感人的短期记忆对4个字符还是比较强的,因此我们可以考虑把错误代码缩短到4个字符不区分大小写,因为如果人在记忆时还要记录大小写的话,难度会增加不少。
  怎么用4个字符表示尽量多的数据呢?数字字母总共有36个字符,理论上使用4位36进制可以表示36x36x36x361679616个值。因此我们只要找到一个针对错误信息字符串的哈希算法,把输出值限制在1679616范围内就行了。
  这里我采用的是MD5作为例子。MD5的输出是128位,理论上我可以取MD5的输出,模1679616就可以得到一个简易的结果。实际上为了减少除法运算,我采用的是取高20位(0xFFFFF)的简易方式(20位二进制的最大值为1048575),然后将这个数字转成36进制的字符串输出。
  当出现异常错误时,我们可以将message的提示信息如下展示:未知错误,错误代码30EV,如需协助,请联系XXX。顺带一提,30EV是Accessdeniedforuserdbuser127。0。0。1的计算结果,这样一来,我就对调用方隐藏了敏感信息。
  至于后台侧,还是需要实实在在地将这个哈希值和具体的错误信息记录在日志或者其他支持搜索的渠道里。当用户提供该代码时,可以快速定位。
  这种方案的优点很明显:能够提供足够的信息,用户可以记住代码,从而反馈给开发侧进行debug。对于同一个错误,由于哈希的特点,计算结果是相同的。即便出现了碰撞,那么只要输入的数据不至于太多,还是能够快速区分的。由于不论多长的错误信息,反馈到前端都只有四个字符,因此后端在记录错误信息的时候,可以放心地基于Go1。13的errorwraping机制进行嵌套,从而记录足够的错误信息
  简易的错误码生成代码如下:import(。。。github。commartinlindhebase36)var(replacerstrings。NewReplacer(,0,O,0,I,1,))。。。funcErr2Hashcode(errerror)(uint64,string){u64:hash(err。Error())codeStr:encode(u64)u64,decode(codeStr)returnu64,codeStr}funcencode(codeuint64)string{s:fmt。Sprintf(4s,base36。Encode(code))returnreplace。Replace(s)}funcdecode(sstring)(uint64,bool){iflen(s)!4{return0,false}sstrings。Replace(s,l,1,1)sstrings。ToUpper(s)sreplace。Replace(s)code:base36。Decode(s)returncode,code0}hash函数可以自定义funchash(sstring)uint64{h:md5。Sum(〔〕byte(s))u:binary。BigEndian。Uint32(h〔0:16〕)returnuint64(u0xFFFFF)}
  当然这种方案也有局限性,笔者能想到的是需要注意以下两点:生成error时要避免记录随机数据、不可重放数据、千人千面的数据,比如说时间、账户号、流水ID等等信息,尽可能使用户进行统一操作时,能够生成相同的错误码。由于数字1和字母I、数字0和字母O很类似,因此需要进行统一转换,避免出现歧义。这就是为什么在Err2Hashcode中,对hash结果encode之后要重新decode一次再返回的原因。
  此外,笔者需要再强调的是:在开发中,针对各种不同的、正式的错误用例依然需要完整覆盖,尽可能通过已有的codemessage机制将足够清晰的信息告知主调方。这种hashcode的错误代码生成方法,仅适用于错误用例遗漏、或者是快速迭代过程中,用于发现和调试遗漏的错误用例的临时方案。

机顶盒装哪个软件看电视直播不卡,直播源也稳定?电视直播不卡,直播源稳定的直播软件其实是有很多的。但很多网友却还是一直在推荐电视家,HDP等这些早已烂大街的直播软件,看电视直播节目不卡顿才怪呢?那么到底有哪些直播软件称……买车是燃油车好还是新能源车好?新能源车销售增长说明了什么?中国人买车,除了少数人把买车当面子、当身份以外,大多数的人买车还是比较务实,把它当做一个代步工具而已。买车时讲究价格合理,节能省油,毕竟老百姓过日子不易,柴米油盐,都要计算,因……雷军签下苏炳添,刘强东拿下肖战,请明星代言成为了一门商业艺术京东与阿里是一对欢喜冤家,每次阿里有点事情,京东总想着把热点给抢过来?开个玩笑而已。时代在变化,要想不被时代淘汰,你就应该随着时代的变化而变化。不知道从什么时候起,……如果马云的公司倒闭了,会对我们产生多大影响?没任何影响,一切正常,比现在更好。普天同庆,皆大欢喜不仅毫无影响,可能还能促进社会繁荣,因为会向社会输送一大批亿万千万富翁可能去创业带动更多就业,聚是一团火,……2021百度AI开发者大会在元宇宙举办中新网北京12月27日电(记者刘育英)百度Create2021(百度AI开发者大会)27日在希壤APP召开,这是国内首次在元宇宙中举办的大会,可同时容纳10万人同屏互动。……仅靠机器人救不了中国制造业作者:周家兵近日,一些大中型工厂利用机器人取代人工,员工数减少几万、几十万的新闻传出。网友评论有趣、有意味。又有几万人没有社保了,大量工人失业,房贷、车贷用什么还,房价还……探秘谁编写了区块链的规则?Dogecoin,一夜之间铸就百万富翁的玩笑货币;CryptoKitties,卡通猫数字交易卡,售价超过10万美元;Pringles风味仅作为NFT数字产品存在,但其售价远远超……iOS14哪个版本好?我给你答案,一个iPhone用户的真实文章开始之前先问一个问题,不知道有多少小伙伴和我一样,经常期待手机更新,因为除了期待新功能之外,也期待流畅性和发热的改善。iOS作为最稳定的系统之一,虽然功能性比不上国内……七夕送礼太烦恼?不妨看看OPPOWatch2,健康才是最好的再过几天就是七夕节了,想必大家都在为送女朋友或男朋友什么礼物而感到苦恼。从男生的角度来看,送口红、送花不仅会显得不够诚意,同时还会被扣上一个大大的直男帽子;而站在女生的角度看,……交了钱没挖着矿,把挖矿公司告上法庭能告赢吗?00:0000:00前几天,北京朝阳法院审理了一件营销公司和区块链公司的比特币挖矿案,这还是北京第一例比特币合同案,所以引起了很多人的关注。具体是这么回事。一个区块……协合新能源(00182。HK)采购风电设备格隆汇4月6日丨协合新能源(00182。HK)公告,于2022年4月6日:(a)买方A浩泰新能源装备有限公司(公司全资附属公司)与供应商订立采购合同A,据此,买方A将以代价A自……人类有史以来最昂贵的太空望远镜即将升空!它的实力到底有多强?中国太空望远镜(CSST)也称载人空间站空天巡天望远镜,预计2024年发射后进入近地轨道,将和中国空间站在同一个轨道绕地飞行,暂定运行期10年。我国太空望远镜究竟实力有多……
联想到底招谁惹谁了昔日民族品牌的代名词联想,时下成为全民声讨的狗熊。三十年河东三十年河西风水轮流转。联想这部企业发展史绝对可以拍成大型连续剧看点满满。剧中主要人物柳、杨、倪三个人是主角。公……新手机品质都啥样?中国移动报告出炉,小米荣耀OPPO成赢家常看手机评测的朋友,对于各种手机排名应该已经司空见惯了,比如DxOMark、DisplayMate、安兔兔,还有各种机构的排名等等。但中国移动的手机评测排名有多少人关注呢?最近……荣耀30一半遗憾,一半邂逅4月15日的荣耀新品发布会,是荣耀终端公司独立后的首场旗舰发布会。除了首发麒麟985之外,本次发布会还正式发布三款荣耀30系列机型,分别是荣耀30、荣耀30Pro、荣耀3……微信支付与云闪付联动了!用手机支付的你快看能有哪些新优势支付行业近期消息频频。8月24日,北京商报记者注意到,微信支付实现了与银联云闪付的互联互通,通过云闪付App可以向微信账户付款。不同于云闪付成为淘宝支付渠道,云闪付与微信支付的……这届的年轻人已经放弃电视了,客厅还有必要放一台电视吃灰吗?针对现如今要不要买电视这个话题每隔一段时间都会被不同的网友提出来,比如在头条问答或者在知乎上都能够看到类似的问题,有时一些网友所给出的回答也确实让人啼笑皆非,比如认为家里需要有……三星GalaxyTabS8系列平板曝光将搭载骁龙898处理器作为当下为数不多还在持续更新平板产品线的厂商,三星在该领域一直有着很高的市场占有率,而继三星GalaxyTabS7系列上市之后,越来越多的用户期待新一代平板GalaxyTabS……独立8个月发布首款高端旗舰机,荣耀瞄准苹果要夺回市场过去一段时间,因为华为缺席,苹果和三星分食了全球高端智能手机市场绝大部分份额。有数据显示,今年一季度,全球智能手机收入突破1000亿美元大关。在收入份额前十的机型中,苹果和三星……重生的陌陌,囿于社交变现怪圈大多数80、90后的社交始于陌生人社交,那时互联网发展初期,很多互联网巨头也才刚刚起步,用得起手机和电脑的家庭也并不是很多,因此很多人可能有社交账号但是并非社交常驻客。随……iPad选择篇学生工作党学生党在不同的价位应该如何选择满足自己日常需求的iPad。iPad分为Mini、Air、iPad、Pro四个系列。首先从年份上选择,20142017年阶段的iPad发展停滞不前……拒绝流浪地球,世界政府首脑峰会聚焦AI未来2月8日12日,被称为世界上最大的年度政府聚会的世界政府首脑峰会(WorldGovernmentSummit,以下简称WGS2019)在阿联酋迪拜召开,聚集了多国首脑、专家和各……丰田国产赛那预售网传加价疯狂!11月初上市随着三胎政策在我国正式落地,MPV这一车型也受到了消费者更热烈的欢迎。一众合资品牌也开始积极布局,试图通过各自的竞争力来重塑市场竞争格局。近段时间,丰田国产车型中,热度最……智能IVR导航拨打400电话调戏一下思必驰智能接线员?您好,欢迎致电思必驰体验中心。我是智能语音助手,您可以在这里体验产品、咨询商务合作,或者体验项目。请问您想体验哪一项呢?最近,思必驰北京智能服务事业部来了一个新的接线员,……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网