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

关于Go错误处理的4个误解!

时间:2023-03-17 21:53:23 科技观察

大家好,我是炸鱼。Go语言中的错误处理机制一直是各大Gophers的热门话题。甚至有人希望Go能够支持throw和catch关键字,以实现与其他语言类似的功能。社区中的讨论从未停止过。今天剑鱼就带大家了解一下大家最关心,也是最容易被误解和排斥的Go语言的一些错误处理:为什么不支持try-catch?为什么不支持全局捕获机制?处理?未来的错误处理机制会怎样?孤独的try-catch在Go1的时候,大家都知道基本撑不住了。于是Go2的想法就冒出来了。为什么Go不支持try-catch组合拳?Go2的想法是去年公布的,所以在2020年有小伙伴趁机提出了类似《proposal: Go 2: use keywords throw, catch, and guard to handle errors[1]》的提案,得到其他语言的支持。知道了?这是该提案的演示,Go1的错误处理:{},errors.New("err")}funcdo()(string,error){d,err:=foo()iferr!=nil{return"",err}s,err:=d.bar()iferr!=nil{return"",err}returns,nil}新提案改造的方法:typedatastruct{}func(ddata)bar()string{throw"",errors.New("err")}funcfoo()(ddata){throwerrors.New("err")return}funcdo()(string,error){catcherr{return"",err}s:=foo().bar()返回,nil}但是很明显,@davecheney在底部回答说“用最强烈的措辞,不”。这会让人感到困惑,为什么这么难?其实早在《Error Handling — Problem Overview[2]》提案的时候,围棋官方就已经明确提到了。Go官方会在设计上有意识地选择使用显式错误结果和显式错误检查。结合《language: Go 2: error handling meta issue[3]》,我们可以知道拒绝try-catch关键字的主要原因是:会涉及到额外的流程控制,因为try的复杂表达式会导致函数意外返回。在表达式级别没有流控制结构,只有panic关键字,它不只是从函数返回。说白了就是设计理念不一致,实现起来也不是很合理。在过去的多轮讨论中,它已经被Go团队拒绝了。反倒是Go团队一直在反反复复回答这个问题,已经不耐烦了,直接整理出了issues版本的FAQ。如果要捕获Go语言中的所有panic,有一个点很多新同学都会遇到不一样的。即如果goroutine出现panic,没有加上recover关键字(有时忘记了),程序就会崩溃。或者认为加入recover可以一劳永逸的保证goroutine派生出的goroutine产生的panic。但现实总是扑朔迷离。经常看到有同学提出类似的疑问:来自Go读者交流群这时候有其他语言经验的同学想到了一把利器。你能设置一个全局错误处理程序吗?比如PHP语言也可以有类似的方法:set_error_handler();set_exception_handler();register_shutdown_function();显然,Go语言中没有类似的东西。为了分类,我们关注以下两个问题:为什么不能恢复捕获更高级别的恐慌?为什么Go没有全局错误处理方法?如果说源码层面的设计,其实就是通过Go的GMP模型和defer+panic+recver分析源码就可以知道。本质上,defer+panic都是挂载在G上的,大家可以查看我之前写的《深入理解 Go panic and recover[4]》,会有更深入的了解。设计思路在本文中,我们不能局限于源代码。我们需要深入挖掘。Go设计者的想法是什么,为什么不支持?在Goissues中,《proposal: spec: allow fatal panic handler[5]》和《No way to catch errors from goroutines automatically[6] 》针对以上问题进行了针对性的讨论。Go团队的负责人@RussCox给出了明确的回答:Go语言的设计定位是错误恢复应该在本地完成,或者完全在单独的进程中完成。这就是Go语言无法跨goroutines从panic中恢复,也无法从throw中恢复的根本原因,这是语言设计层面的思维决定的。在分析源码的时候,你看到的一整套GMP+defer+panic+recover机制都是按照这个设计思路编写开发的。设计思维决定源码实现。建议的方法是从Go语言层面动摇这种设计思路。目前来看,可能性很低。至少2021年没有看到任何改善。一般建议提供公共的Go方法来规避这种情况。recovery.SafeGo(logger,func(){method(allparameters)})funcSafeGo(loggerlogging.ILogger,ffunc()){gofunc(){deferfunc(){ifpanicMessage:=recover();panicMessage!=nil{...}}()f()}()}是不是感觉很眼熟?每个公司的内部库都应该有这样一个工具方法,避免偶尔遗忘的goroutinerecoverquestion带来的陌生感。也可以参考建议,使用一个单独的进程(Go语言中的goroutine)来统一处理这些panic,但是这样比较麻烦,也比较少见。未来会发生什么?Go社区非常关注Go语言未来的错误处理机制,因为Go1已经完成,希望解决Go2中错误处理机制的问题。期望Go2核心解决以下问题(#40432):对于Go2,我们希望使错误检查更具可移植性,减少专用于错误检查的Go程序代码量。我们还想让编写错误处理变得更容易,减少程序员花时间编写错误处理的可能性。错误检查和错误处理都必须保持显式,即在程序文本中可见。我们不想重复异常处理的陷阱。现有代码必须继续工作并像今天一样有效。任何更改都必须与现有代码兼容。为此,很多人提出了很多新的提议……遗憾的是,截至2021年8月下旬,已经有很多人试图通过改变语言级别来实现这些目标,但新提议都没有被接受。现在也有很多改动合并到Go2提案中,主要是错误处理的优化。有兴趣的可以看看我之前写的:《先睹为快,Go2 Error 的挣扎之路》,相信能给你带来不少新知识。总结看到这里,我们不禁会想到。为什么,为什么,21世纪了,前有那么多优秀的语言,而Go语言的错误处理机制还是那么难选?显然,Go语言的开发团队有自己的设计理念和思路,否则“少即是多”也不会流传得如此广泛。理解Go官方的思路到位,而不是一味的单向理解,对我们以后的编程道路会更好。当然,这里面还有一系列必要和必要的问题,很难处理。欢迎大家关注建宇,我们也可以继续关注和讨论Go后续的错误处理!参考[1]提案:Go2:使用关键字throw、catch、guard来处理错误:https://github。com/golang/go/issues/40583[2]错误处理——问题概述:https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md[3]语言:Go2:errorhandlingmetaissue:https://github.com/golang/go/issues/40432[4]深入理解Gopanicandrecover:https://eddycjy.com/posts/go/panic/2019-05-21-panic-and-recover/[5]提案:规范:允许致命恐慌处理程序:https://github.com/golang/go/issues/32333[6]无法自动捕获goroutines的错误:https://github.com/golang/go/issues/20161