程序健壮性程序运行时难免会遇到各种错误。其中一些错误包含在最初的逻辑判断中。还有一些是程序描述的,但我们认为这不是正常逻辑的一部分。不管是什么形式的问题,我们在编写我们期望的业务逻辑的时候,都不可避免的要针对程序的异常进行编程。为了在异常中保护程序流程的正确性,不让“过多”的功能不可用,我们需要针对异常进行编程。从而保证了程序的健壮性。一般来说,如果我们能够覆盖系统中的所有异常,那么我们的程序就会变得非常坚不可摧。显然,程序的健壮性在这个时候就体现出来了。不可原谅的可读性程序的健壮性是非常必要的,因为它关系到系统的稳定性。因此,系统的健壮性是我们不能放弃的。但是不得不说,如果我们只关心系统的健壮性,那么我们很容易把代码弄得非常糟糕,难以阅读。比如,不管是不是出自我们自己之手,有时我们都会看到这样的代码:Aa=getA();if(a.status!=xxx){Bb=a.getB();if(b.status!=xxx){doSomething(b);}}从程序的角度来看,当程序的返回结果不满足主流程的需要时,就不会执行我们的逻辑。从程序的健壮性来看,以上部分代码已经满足了业务的需求。可是他怎么了?如果去掉状态判断,可以简化为如下逻辑:Aa=getA();Bb=a.getB();做某事(b);这个逻辑很清晰,分为三部分:获取变量a,然后获取a的属性b,然后根据b进行某种操作。虽然在正常流程中上下代码的处理是一样的,但是上面的代码处理的是异常流程。但是我们很容易发现问题所在:异常逻辑的不优雅处理会使程序难以阅读。虽然我们处理了异常,但如果代价是让我们的代码逻辑混乱,难以阅读。所以我不认为这是处理它的正确方法。错误逻辑的优雅处理我们希望在不损失程序可读性的情况下保证系统的健壮性。而一些编程原则可以帮助我们实现我们的目标。使用异常,我们有许多繁重的方法来描述一个程序是否符合我们的期望。例如,在上面的第一个例子中,我们通过A和B对象中的状态字段来反映逻辑是否按照我们的预期实现。这个例子中的字段并不是那么合理,但是有时候这种处理方式是因为对象本身的属性中包含了描述状态的字段,对于不需要的数据我们在后续逻辑中直接复用这些字段来处理。但这有一个问题:我们将处理异常的逻辑与处理正常逻辑的代码混合在一起。由于状态是分散在不同的对象上的,所以我们需要在使用对象之前对每个对象进行判断。第一个问题使代码的可读性降低。第二个问题导致在执行上层编码时依赖于下层的细节。如果编码人员不知道底层的异常逻辑,他将错过处理异常的机会。而如果我们使用异常来处理,代码就可以变成:try{Aa=getA();Bb=a.getB();做某事(b);}catch(AStatusExceptione){e.printStackTrace();}catch(BStatusExceptione){e.printStackTrace();}catch(Exceptione){e.printStackTrace();}可以看到这种处理失败过程的方式可以保持“正常的业务逻辑”完好无损,其中try包裹的部分和做的部分完全一样上面没有做异常判断,所以:建议使用异常(Exception)状态代替失败状态,保证正常业务逻辑的完整性。进一步的,在《如何写好一个方法》一文中,我们提到可以将异常单独封装为一个方法,这样一个方法中只关注一件事,那么我们可以这样调整:try{tryDoSomething();}catch(AStatusExceptione){e.printStackTrace();}catch(BStatusExceptione){e.printStackTrace();}catch(Exceptione){e.printStackTrace();}...privatevoidtryDoSomething(){Aa=getA();Bb=a.getB();doSomething(b);}这样调整之后,我们就可以把异常处理部分分离出来了。这样,如果我们只关心正常的业务逻辑,我们就只关心tryDoSomething()。封装异常观察上一节的代码,我们可以发现,对于tryDoSomething()方法,我们进行了两种指定类型的catch操作,但是在目前的代码中,这两种类型的处理动作逻辑并没有区别ofcatches,这意味着当前业务逻辑不关心异常类型。此外,异常类型实际上是底层方法的实现细节。因此,存在一个相互排斥的问题:底层实现(或公共API)想要提供有关异常场景的足够信息。上层实现并不关心所有的异常信息。针对这两种情况,我们可以通过对异常的封装,将下层异常抽象出来,从而屏蔽上层调用的细节。方法如下:try{tryDoSomethingWithSameException();}catch(StatusExceptione){e.printStackTrace();}...}catch(AStatusExceptione){thrownewStatusException(e);}catch(BStatusExceptione){thrownewStatusException(e);e){抛出新的StatusException(e);}}tryDoSomethingWithSameException()这个方法可以定义在一个单独的代理类中,也可以通过其他方式定义,但是一般情况下,通过使用tryDoSomethingWithSameException()方法,我们真正在最外层调用的时候,只需要关心这个状态异常的方法。同时,由于使用了tryDoSomethingWithSameException()方法,如果我们在tryDoSomething()中调整业务逻辑;并且产生了新的异常,我们不需要调整主要业务逻辑的文件,只需要调整异常封装类即可。好了,让我们少修改主要的业务流程。Uncheckedexception你会发现无论是tryDoSomething()还是tryDoSomethingWithSameException()都没有在上面的方法中使用throw。也就是说,我们正在使用“未经检查的异常”。那如果我们使用“checkedexception”呢,代码会变成这样:privatevoidtryDoSomething()throwsAStatusException,BStatusException{Aa=getA();Bb=a.getB();doSomething(b);}方法中,需要定义方法中抛出的异常。可能有人会觉得这个方法很好,因为足够清楚,一眼就能知道会出现什么异常。而我们在上层使用的时候,也可以直观的知道方法可能出现的异常。但是这种方法的优点也变成了缺点,因为对异常的描述直接变成了方法签名的一部分。并且因为是checkedexception,所以会一步步往上传递,直到上层捕获并处理。也就是说,如果不想在当前方法中处理异常,就需要在方法签名中加入异常。这样一来,当调整一个底层逻辑,添加异常时,所有调用该方法的方法都需要调整,这显然不符合开闭原则。当然,对于下面的关键逻辑,可能需要让开发者知道可能存在的异常。但是对于更一般的情况,使用uncheckedexception更适合代码的可维护性。特殊对象而不是异常当我们尝试获取列表时,我们可以使用以下方法:List
