当前位置: 首页 > 科技观察

Go2ErrorHandlingProposal的批评

时间:2023-03-12 06:53:03 科技观察

ReviewGrammar2018年8月,Go2DraftDesigns[2]正式公布,其中包含对泛型和错误处理机制的改进的初步草案:Go2DraftDesigns以下是重点Go2错误处理的新语法。错误处理(ErrorHandling)首先要解决的问题是大量的iferr!=nil问题,Go2错误处理的设计草案[3]就是为此提出的。简单例子: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)或检查错误,这会将此标记为显式错误检查。其次引入了handle关键字,用于定义errorhandler的流程,一步步往上抛,以此类推,直到handler执行完return语句,才正式结束。ErrorPrinting第二个要解决的问题是ErrorValues和ErrorInspection的问题,这就导致了ErrorPrinting的问题,也可以认为是格式错误不方便。针对此,官方提出了ErrorValues[4]和ErrorPrinting[5]的设计草案。一个简单的例子如下:iferr!=nil{returnfmt.Errorf("writeusersdatabase:%v",err)}优化方案如下:packageerrorstypeWrapperinterface{Unwrap()error}funcIs(err,targeterror)boolfuncAs(typeE)(errerror)(eE,okbool)本提案增加了错误链的WrappingError概念,同时增加了errors.Is和errors.As的方法time,和上面说的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{...}它通过使没有上下文信息的情况下返回错误变得容易而违背了“增加开发人员编写错误处理程序的可能性”的目标。将减少需要的使用频率handle和check函数的handle和check函数变成了可有可无的东西注:个人感觉这个既黑又粉……原作者反黑了?当然如果err!=nil真的很容易上手。复杂的错误链对于下面的例子,看看它是什么感觉……代码如下:/notthatfor...{handleerr{...}//也不是那个...}}handleerr{...}//其次this...if...{handleerr{...}//notthat...}else{handleerr{...}//首先thischeckthisFails()//trigger}}显然,这段代码是“难以捉摸的”,我们必须通过眼睛解析整个函数,理解整个错误处理的流程和顺序。会增加我们对程序的认知负荷。总结通过回顾Go2错误处理的设计稿,我们了解了check和handle函数的用法和思路。然后,针对新的语法,“批判”可能出现的新问题。总的来说,新的语法在缺点上会增加现有代码的复杂性和可读性,会造成各种奇怪的嵌套,如果err!=nil也会重复,成为一种新的处理方式(onemore)。是不是会还不如iferr!=nil那样的纯粹?参考资料[1]Golang,howdareyouhandlemychecks!:[2]Go2DraftDesigns:https://go.googlesource.com/proposal/+/master/design/go2draft.md[3]Go2错误处理:https://github.com/golang/proposal/blob/master/design/go2draft-error-handling-overview.md[4]错误值:https://github.com/golang/proposal/blob/master/design/go2draft-error-values-overview.md[5]打印错误:https://github.com/golang/proposal/blob/master/design/go2draft-错误打印.md