C#避免在函数或者操作中抛出异常
引言
如某个场景下,你的函数或操作需要操作一个序列的对象,且在处理的过程中抛出了异常。这时如果没有一些状态记录之类的数据,我们不了解已经处理了多少的数据,也不知道应该采用怎么样的策略回滚,因此根本无法返回到之前的状态。 我们看一下下面的一个代码:
|
|
这样的代码看起来没什么问题。可是某一天,这个程序运行时抛出了异常。抛出异常的位置可能未知,导致部分员工得到了加薪,另外的一些员工却没有。结果是除了人工检查数据,我们已经没有办法重新找回丢失的状态。
这样的代码修改元素的方式导致发生了上面的问题。这段代码并没有遵循“强异常安全保证”规则。换而言之,在运行时遇到错误,我们无法得知具体发生了什么,没有发生什么。
原则:如果我们能保证当方法无法完成时,程序的状态不会发生改变,这样的问题就不会发生。
我们有几种方法都可以实现这样的需求,但是每种方法都有各自的优势和风险。
在函数/操作中抛出异常
显而易见的,不是所有的方法都会遇到这样的问题(异常导致状态丢失)。很多时候我们只是检查了一下序列中的元素,访问之后并不会修改其中的元素。这类的行为我们其实并不需要太过于小心。现在我们回到最开始的地方,对于上面的场景(为每位员工加薪百分之五),如果我们想遵循“强异常安全保证”原则,那应该如何修改这个方法呢?
第一种异常:获取数据的时候异常
在上面的例子中,即使FindAllEmployees()函数抛出异常,导致我们无法正确让员工加薪。虽然这样的情况并不是导致我们的数据产生问题,但是该加薪的大家没有得到加薪,这是一个多么沮丧的事情呢。
解决方法:重写前面以lambda表达式给出的操作方法(即FindAllEmployees()方法),让其永远不会抛出异常。
很多时候,我们在开始修改数据之前,先校验数据的合法性以及剔除错误数据(如果允许剔除的话)并不是非常困难。我们是可以采取这样的方法来实现我们的目的。不过在这里的话,我们就必须严格处理操作方法,使得它能满足所有情况下的需求。
第二种异常:lambda表达式中操作数据异常
同样是上面的例子,如果我们在执行加薪操作的时候,提升了那些已经离职的员工薪资导致了异常,使得程序中断,状态丢失。这样的情况,我们在执行加薪操作前先过滤掉已离职的员工便是一种正确的做法。
解决方法:操作数据前通过校验过滤后再执行操作
如: allEmp.Where(emp=>emp.Active).ForEach(e => e.MonthlySalary *=1.05M);
第三种异常:执行操作的时候抛出异常
有时候,我们根本无法保证处理方法的时候会不会抛出异常。这个时候就必须采取一些代价更加昂贵的处理方法了。
解决方法:创建副本尝试执行操作,副本无误后执行真正操作
我们在编写这类代码的时候,应该考虑抛出异常之后的处理方案。这就意味着,我们的操作应该先在原数据副本上执行,随后仅在操作成功之后再将其替换原有的数据。 如:
|
|
但是这样的修改也引发了其他的问题:代码量增加了,同时生成副本也消耗了大量的资源。这样的做法也有一个好处,我们在操作副本数据时遇到异常之后,有充分的"空间"来处理这些数据。
实际中,这意味着我们让查询表达式返回了新序列,而不是去修改原先序列中的元素。这样的话,我们在尝试完成所有的操作的同时,即使失败了,也不会影响到我们程序的原有状态。