论程序中的错误处理

来源:互联网 发布:淘宝的卡卷包在哪里 编辑:程序博客网 时间:2024/05/16 18:07
     一直以来,都对程序中的错误处理存有许多疑惑。介绍程序本身的文章有很多,但是介绍对错误的处理的却甚少。经过在三年的编码实践中,总结出了一些对错误处理的原则,现写下来,仅作为参考和讨论。
     
     我对“错误”的理解:程序运行时与预期不一致的场景。导致与预期不一致的原因有多种,错误的可能情况,随着操作主体的增加,会变得难以完全罗列。
      总结下来,错误主要分为两种:“逻辑不一致”和“物理不一致”。其中“逻辑不一致”表示一个操作执行之后,得到了结果,但是结果跟预期不一致。比如查询用户的状态,预期用户状态应该为“锁定”,但实际上状态是“正常”。“物理不一致”是指一个操作在执行过程中,并未得到结果。比如查询数据库时,数据库返回了一个错误。
     以上是对基本概念的一些解释。接下来,我给出了几个在处理错误过程中的规则。

1、千万不要把bug当做错误
     在头脑中思考对错误的处理时,最让我苦恼的就是,各种繁杂的“与预期不一致”的情况混在在一起,无法对其进行分类,然后分门别类的加以分析,从而也就难以在程序中精确地对这些情况进行反馈、处理。注意我刚用了个“与预期不一致”,而不是“错误”,这是因为这些“与预期不一致”的情况并非都是错误,其中一部分,应该被定性为“bug”。比如用户的状态本应该为“锁定”,但实际上却是“正常”,如果按照系统完全正常、没有bug的情况,绝对不应该出现状态为“正常”的情况,那这个场景因该被定性为“bug”,并且应当在测试阶段予以暴露并解决。否则,这些bug隐藏在程序中,被当做错误来处理,而由于bug所导致的“预期不一致”的情况,往往是难以自动纠正的。
     bug与错误的区分,我认为应当是“获取到了结果,但结果错了”与“获取结果的过程出错了”的区别。对于前者,在开发与测试阶段,应当尽可能的让其暴露出来,以便予以修正。但是程序到了一定规模,往往bug是很难完全排除的,这一点需要在实践中不断积累,提高程序的可靠性以及让bug暴露的概率。这其中最忌讳的,就是把bug当做错误,一并处理,这样的系统跑起来后,绝对会让你崩溃。
     bug既然不能归入“错误”,那么在程序中对这一类“与预期不一致”的情况,要给予严重警告甚至报错退出,这其中可以利用断言之类的操作,让其容易被发现,并且被及早解决。
     另外,无论是bug还是错误,都应该对所有可能出现的点进行控制,不应当有忽略错误的情况发生,除非这个错误被认定为允许被忽略。但这个时候,这更像一个状态,而不是一个错徐。

2、利用原子操作
     典型的原子操作就是数据库的事务,利用数据库的该特性可以做到一系列的操作“要么完全成功”,要么“什么都没做”的效果。避免出现不一致的数据。但是如果碰到数据库操作中间穿插着其他系统的操作,那这些操作往往难以组成一个事务。同时由于数据库的操作和其他系统的操作都存在出错的可能性,导致“一定会出现不一致数据”的场景出现。对此我不知道是否存在优秀的解决方案,对此也欢迎加以讨论,完善这部分内容。今天跟领导讨论下来,领导给出了一个很不错的方案。方案基本上是模拟事务的处理。针对刚才提到的数据库操作与第三方系统操作并存的情况,可以先让数据库操作成功,并且成功在一个中间状态上。然后再去执行第三方系统的操作,并根据操作反馈,来更新数据库的中间状态。这时候,前面那位蹲着的同学提出来了:“你数据库更新中间状态时,仍然有可能出错,还是会导致数据不一致的情况发生!”。这位同学的分析是对的,的确会出现这种情况。请看下一条原则。

3、尽量设计可重入的过程(函数、方法等)
     可重入的概念在数学上称作“幂等性”,是说一个操作做1次和做n次的结果是一样的,比如说计算1的平方这个操作,你无论做多少次,结果都是1,那这就是个幂等性的操作。应用到程序中呢,可以说执行一个函数,执行1次和执行n次,结果是一样的。当然其中一次如果出错了,那继续执行,有可能得到正确结果。这个理论用于处理第2调最后提出的问题,如果第三方系统操作未得到结果,那应当有相关的操作,可以查到上一次操作的结果,根据不同的结果,来重新执行,或者是修正数据库状态。如果这整个操作设计成可重入的,那在外部调用这个过程时,就可以简单地循环调用,直到返回正确结果。如果碰到操作实在无法设计成可重入或者为了实现可重入要付出很重的代价,可以考虑将操作拆分,尝试将可重入的操作与不可重入的动作拆分,从而尝试整个操作的可重入设计。

4、错误可以包装后向上层提交
     这个原则是出于亮点考虑。第一点,一个错误应当由“能够决定出现这个错误之后接下来该怎么办”的逻辑来进行解释并处理,而不是随意在某个节点进行解释并处理;第二点,每一层都不可能知道这个错误的完整原因是什么,需要每一层将自己所知道的信息进行汇报。第一点好理解,第二点比较像设计模式中的“装饰者模式”,当前层级对错误进行装饰,并将装饰了本层次信息的错误向上提交。比如说,在最外边的层次可能得到这样一个错误信息“锁定用户失败:更新用户信息失败:执行数据库更新失败:数据库连接已断开”,如此可以清晰地用类似于调用栈的形式来展现错误的来龙去脉,十分的明确。同时,还可以将错误对象设计成嵌套模式,这样就可以解析出每一层的错误信息。不过我认为必要性不大,你知道了,也难以对每一种错误做出针对性的处理,还不如利用第3条,通过重入的方式来修正系统。


备注:本文允许随意转发,但转发时需带有原文章作者以及原始链接信息,谢谢。
      
2 0