如何看待gopanic及异常处理

Posted 2021-04-16 13:47 +0800 by ZhangJie ‐ 1 min read

分享:  

Background

最近有同学提问,大意是:“go中什么时候用panic、什么时候用error,能不能像其他语言中的try-catch一样用panic-recover来代替层层return err,或者应不应该recover一个panic之后转换为error?”

这个问题引起了广泛的讨论,在对这几个问题的理解上,我本以为大家应该会认识到位的,没想到很多人认识很模糊。当然,好的地方就是总有有见识的同学站出来指出大家的问题。

对于那些有灵性的同学,勤实践勤思考的同学,他会自然而然意识到哪种error handling pattern更好,也会有意识地去区分不同pattern的定位和应用场景。这类同学虽然没有什么理论术语支撑,但是他们的“经验”是贴近更好的设计思想、最佳实践的。如果更进一步,能愿意接受一些设计思想的洗礼,则可以将“经验”上升到“模式”,以指导更多人。

panic != exception

go panic不同于其他语言中的exception,在设计、定位上是有明确的区别的,see: https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right。

panics are always fatal to your program. In panicing you never assume that your caller can solve the problem. Hence panic is only used in exceptional circumstances, ones where it is not possible for your code, or anyone integrating your code to continue.

go panic是用来表示程序出现了十分致命的错误,并且你不能假定这个错误能被解决。所以panic只在很少的场景下才会被用到,并且出现panic时,你的代码解决不了,引用这部分代码的其他代码也解决不了。

所以,panic并非一般意义上的error,更不能用panic-recover代替层层向上传递error

对于,为了自身程序的健壮性,而在启动新的goroutine时,或者调用外部依赖的导出函数、方法时,可能选择recover一些预料之外的panic,并转换为error处理。

有追求的开发人员,在panic的使用上应该始终遵循go设计理念,同时在程序的健壮性上也会采用些防御性编程的手段。

panic vs exception

我们很多开发人员都接触过多门语言,比如Java、C++,等等,这类语言都有异常处理机制,遇到一些意外事件时可以抛出一个异常,异常通常由try-catch block捕获并处理。

初学者阶段,很多同学会努力去学习异常处理的正确编码方式,甚至是异常处理的实现原理,对性能的影响,等等,但是由于实际缺乏实际的大规模工程供锻炼实践,也很少有人会去思考一些问题,比如:

  • QA:我们为什么需要异常?

    • 层层返回error,编码不方便,希望有统一的错误处理逻辑,保证主逻辑更清晰。
  • QA:异常解决了什么问题?

    • 避免了层层传递error,异常在统一位置处理。
  • QA:异常引入了什么问题?

    • 区分异常发生的位置,就要每个位置定义一个异常类型,这个数量应该挺大的。

    • 而且由于实际编码中同一个异常会在多处被抛出,实际上看到代码中捕获一个异常类型时,你很难断定它是哪个操作抛出的。

    • 而且每个可能捕获这个异常的地方,都需要拷贝异常处理代码。

    • 如果没有捕获异常,通常进程会挂掉,能否识别一个函数是否会抛出异常,Java中有checked exception、unchecked exception,前者可以在编译时帮助确定是否有遗漏的try-catch,但是仍然有unchecked exception。

  • QA:异常真的解决了问题么?

    • 异常表面解决了老问题,但是却有引入了新问题,而且新问题似乎更严重。

异常+try-catch,本质上将当前操作的错误处理逻辑转换为了caller要解决的问题,并没有少写多少错误处理代码,反而,同一异常处理代码在多个try-catch中被拷贝,而且可读性更差了。错误发生地、错误处理地分散在不同地方,能说是可读性好吗?我不这么认为。

don’t need exception?

异常处理,真的是个好东西么?它真的解决了问题么?

之前同困惑c++异常是解决了问题还是引入了新问题,为此也多方了解,直到后来看到zmq之父Martin Sustrik的文章,Why should I have written ZeroMQ in C, not C++。Martin详细介绍了在错误处理过程中,如果采用C++异常处理会带来怎样的麻烦,而如果直接使用error处理会有哪些好处。最终Martin在实现zmq时采用了 c++ minus exception的技术路线,即使用C++但是不适用C++异常处理。

基于以前的沉思,Martin后面使用C有重新写了一个zmq的进化版本nanomsg ,C没有异常处理 :)

这里不想引战,也不想做二元的判定,强烈推荐读者朋友们阅读下Dave、Martin Sustrik的文章,相信会对panic、error、exception的设计理解更透彻。

References

  • Why go gets exceptions right, https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right
  • Why should I have written ZeroMQ in C, not C++, https://250bpm.com/blog:4/