Hi,我是建宇。一年半前,我分享了《先睹为快,Go2 Error 的挣扎之路》的一篇文章,其中涉及到Go1错误处理的问题,Go1.13的增强,以及Go2新的错误处理提案的详解。你们中有多少人还记得Go2新漏洞提案的“美好”未来?当时Go2的新提案也遭到了很多非议,@LiamBreck在《Golang, how dare you handle my checks!》中进行了非议,大家一起学习吧!语法回顾2018年8月,Go2官方DraftDesigns正式公布,其中包括对泛型和错误处理机制的改进的初步草案:以下是Go2错误处理的关键新语法。错误处理(ErrorHandling)首先要解决的问题是大量iferr!=nil的问题,为此提出了Go2错误处理的设计草案。简单例子:iferr!=nil{returnerr}优化方案如下:funcCopyFile(src,dststring)error{handleerr{returnfmt.Errorf("copy%s%s:%v",src,dst,err)}r:=checkos.Open(src)deferr.Close()w:=checkos.Create(dst)handleerr{w.Close()os.Remove(dst)//(仅当检查失败)}checkio.Copy(w,r)checkw.Close()returnnil}Mainfunction:funcmain(){handleerr{log.Fatal(err)}hex:=checkioutil.ReadAll(os.stdin)data:=checkparseHexdump(string(hex))os.Stdout.Write(data)}这个提案引入了两个新的语法形式,第一个是check关键字,可以选择一个表达式checkf(x,y,z)或checkerr,这会将其标识为显式错误检查。其次引入了handle关键字,用来定义errorhandler的流程,一步步往上抛,以此类推,直到handler执行完return语句,就正式结束了。ErrorPrinting第二个要解决的问题是ErrorValues和ErrorInspection的问题,这就导致了ErrorPrinting的问题,也可以认为是格式错误不方便。针对于此,官方提出了ErrorValues和ErrorPrinting的设计草案。一个简单的例子如下:iferr!=nil{returnfmt.Errorf("writeusersdatabase:%v",err)}优化方案如下:packageerrorstypeWrapperinterface{Unwrap()error}funcIs(err,targeterror)boolfuncAs(typeE)(errerror)(eE,okbool)本提案增加了错误链的WrappingError概念,同时增加了errors.Is和errors.As的方法,这与上面提到的Go1.13的改进是一致的,这里不再赘述。Go1.13没有实现%+v输出调用栈的要求(没有调用栈,排查问题会很麻烦),因为这样会破坏Go1的兼容性,造成一些性能问题,而且很可能是在Go2中添加。批评提案的目标相对模糊在Go2新错误处理的提案和草案中,@LiamBreck认为没有讨论根本需求。只有一个简短的目标部分,如下四点:Smallfootprinterrorchecking。开发人员友好的错误处理。明确检查和处理错误。保证与现有Go1代码的兼容性。没有提及未来可能的扩展,只是纯粹为了满足当前的需求。这个类别是模糊的,并且在激发新的设计思想方面是有限的。无法统一错误处理在实际应用中,一个函数使用两种或多种重复的错误处理方法是很常见的。以下代码://方法1{debug.PrintStack();log.Fatal(err)}//方法二{log.Println(err)}//方法三{iferr==io.EOF{break}}//方法四{conn.Write([]byte("oops:"+err.Error()))}//在新的网络服务器提议中,检查和处理函数没有提供多种方式来处理错误。这是一个明显的遗漏,无法统一错误处理。在这种情况下,check和handle只是增加了一个新的方法,使得原有的错误处理机制变得更加复杂。err!=nil和checkhandle函数混用是后进先出的模式,只能跳出一个函数。也就是说,它不能很好地处理可恢复的错误内容。但实际上,许多方法返回错误是很常见的。因此,我们需要同时使用err!=nil和check,非常繁琐。以下代码:handleerr{...}v,err:=f()iferr!=nil{ifisBad(err){checkerr}//恢复代码}isiferr!=nil,再次处理,另一个检查功能。嵌套检查更复杂有了检查功能,嵌套调用返回错误的函数调用会混淆操作顺序。虽然在大多数情况下,发生错误时调用的顺序应该很清楚,但在check函数下不如iferr!=nil清楚。下面的代码:checkstep4(checkstep1(),checkstep3(checkstep2())此外,嵌套代码会鼓励不可读的结构:f1(v1,checkf2(checkf3(checkf4(v4),v3),checkf5(v5))现在回想起来,语言是禁止的。f(t?a:b)和f(a++)是不是有点讽刺?如果err!=nil太好用了Go1的错误处理程序太友好了,即:iferr!=nil{...}它通过在没有上下文信息的情况下轻松返回错误而违背了“增加开发人员编写错误处理程序的可能性”的目标。注意:个人认为这是黑色和粉色都...原作者把黑色反了?当然如果err!=nil真的很容易上手。复杂的错误链对于下面的例子,看看它是什么感觉……代码如下:/notthatfor...{handleerr{...}//也不是那个...}}handleerr{...}//其次this...if...{handleerr{...}//notthat...}else{handleerr{...}//首先thischeckthisFails()//trigger}}显然,这段代码是“难以捉摸的”,我们必须通过眼睛解析整个函数,理解整个错误处理的流程和顺序。会增加我们对程序的认知负荷。总结通过回顾Go2错误处理的设计稿,我们了解了check和handle函数的用法和思路。然后,针对新的语法,“批判”可能出现的新问题。总的来说,新的语法在缺点上会增加现有代码的复杂性和可读性,会造成各种奇怪的嵌套,如果err!=nil也会重复,成为一种新的处理方式(onemore)。它会比iferr!=nil更不纯粹吗?Go书系列Go语言入门系列:初探Go项目实战Go语言编程之旅:深入使用Go做项目Go语言设计哲学:理解Go的Why和设计思维把Go代码变成意大利面条?仅当err!=nil时才去?不对,把这些优雅的搬运姿势分享给你!
