您的位置:首页 > 其它

异常处理的最佳实践(上)

2015-06-29 14:53 232 查看
在开发一个系统的时候,我们往往会将大部分注意力集中在如何实现具体的功能上,而对于异常和错误处理则通常不受重视——感觉这事情无从下手,又看不到什么政绩,简直吃力不讨好。而异常处理本身,也常常被作为语言特性的一部分轻描淡写地一笔带过,很少有系统性地对其正确的使用方式进行介绍。这简直是暴殄天物!使用异常来处理错误明明是一项伟大的发明,是将程序员从条件判断的地狱中拯救出来的神器!本文将给大家介绍一下异常处理的一些最佳实践。

异常的错误处理方式

很多程序猿害怕异常,他们觉得异常是万恶之源,会弄停他们的软件——太阳当空照,花儿对我笑,程序欢快地跑着,突然来了一个莫名其妙的错误输入,就崩溃了——这是多么令人讨厌的事情啊!于是他们对异常处理的理解往往仅限于把异常catch住,不要去打扰系统的正常运行,就像这样:

稍有研究的程序猿甚至懂得使用例如一个全局的UncaughtExceptionHandler来一劳永逸地”解决”这个麻烦。

然而这种行为的本质是隐藏错误——早在上个世纪,伟大的VisualBasic曾发明了个叫On Error Resume Next的神奇咒语来满足类似的需求,其结果是臭名昭著的。对错误的发生采取鸵鸟对策,往往会造成无法预料的结果,令程序运行到无法预知的状态。一旦遇到真正需要解决的问题,即使消耗大量的人力物力也难以定位原因,甚至直接导致项目的失败。

另一种常见的异常处理方式是使用返回码——catch到异常后,便返回一个错误代码。这也是异常恐惧症的一种典型症状:觉得异常是可怕的杀手,把他转成温和的返回码,就变得无害了。可惜返回码并不是什么好东西,其意味着一个需要不断维护的庞大列表(然而这事太麻烦了实际上并不维护)。程序猿需要在正常逻辑中追加各种条件判断来处理无穷无尽的特殊情况(然而错误码往往被简单忽略实际上并不判断)。正确地使用返回码极其麻烦,而一旦返回码没有被正确地使用,它就会化身为和忽略错误一样麻烦的害兽,吞噬着软件的质量。

异常处理的正确策略

异常被发明出来绝对不是为了仅仅弄停你的程序。大家遇到的crash,往往是因为未捕获的异常。未捕获的异常,意味着系统不知道如何处理这个异常,于是最妥当的方式就是止损,不再继续运行以免发生不可预料的操作。那么我们应该在何时捕获异常呢?只有当我们知道发生问题时,应该如何恢复到一个安全的状态的时候。有了一个安全的状态,我们就可以保证之前的错误不影响后续的数据处理。

简单全局策略

异常最基本的价值在于可以携带大量的信息,尤其是错误消息和StackTrace,对于定位问题具有很大的帮助。因此一种简单的异常处理策略是:在开发阶段不处理异常,任由crash发生,并通过其中的异常信息来定位问题。在正式发布以后,加入一个简单的全局异常处理单元,其功能是记录下异常信息并使用合适的方式上报。许多客户端软件都采取了类似的策略,赌的是通过测试来尽可能暴露问题,并利用异常便于定位问题的特性大幅提高bug修复的速度。只要发布前做了充分的测试(呵呵),真正会发生crash的场景很少。

针对服务端请求的处理策略

服务端程序猿最害怕crash,一旦停止服务,负责人往往会被搅了清梦,结果一查通常只是个非法输入,采取的措施也不过是重启,因此他们会有很强的动机写一个什么都不干的全局异常处理函数(最多打打log)。

隐藏错误的危害这里就不多说了,上述处理方式往往还会造成客户端无法及时得到响应只能坐等超时。有些Web框架带有默认的异常处理策略,虽然不会crash,但常常导致异常信息泄露给调用者,造成不必要的安全隐患。

所幸对于服务端来说,请求的处理往往是相互独立的,于是只要抛弃那个出现错误的请求,即可回到安全状态。因此对于服务端程序来说,通常理想的异常处理策略是针对每一个请求的入口进行try-catch,将异常catch住后封装成对调用者友好,并且不会泄露系统信息的错误消息,返回给调用者。于此同时,将详细的错误消息及请求内容记入日志。后续进行troubleshooting时,可以根据时间戳、错误码、请求id等查询到日志中的相应记录。

针对用户操作的处理策略

对于一个需要长期稳定工作的客户端软件来说,简单的全局异常处理策略往往是不够的。每个Crash的发生,都会影响用户的体验和程序猿的业绩,即使再充分的测试,也难以避免漏网之鱼,尤其是环境因素造成的诡异问题。

对于客户端软件来说,业务逻辑的入口往往是用户的操作。每当旧操作完成之后,新操作发生之前,往往意味着独立的业务逻辑暂告一段落,可以视为安全状态。因此一个较为妥当的策略是在每个用户操作的处理函数中,均加入try-catch。异常catch住时,显示一个对用户友好的报错信息,同时将异常信息以及用户操作的相关数据记录并上报。

注:如果用户操作触发了一个进度条之类提示进展的界面,需要在异常处理逻辑中对UI进行重置。

小结

前文所述的策略主要侧重在最上层的异常处理方式,原则上所有从底层抛上来的异常均会在这里做最后的处理。绕过这层,便会成为未捕获的异常。通常情况下,底层即使捕获了异常也做不了什么,而这里才是真正能够对错误的发生采取措施的地方。

因此正确的异常处理方式实际上是非常简单直接的:什么都别做,交给最上层处理;最上层也只需报错,记录,然后恢复到安全状态继续执行后续逻辑。

未完待续

本文介绍了大体的异常处理策略,然而还有一些细节可以帮助我们更好地在异常发生时定位问题。欲知后事如何,且听下回分解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: