- 7 错误处理
- 7.1 使用异常而非返回码
- 7.2 先写try-catch-finally
- 7.3 使用未检异常
- 7.4 给出异常发生的环境说明
- 7.5 依调用者需要定义异常类
- 7.6 定义常规流程
- 7.7 别返回null值
- 7.8 别传递null值
- 7.9 小结
- 8 边界
- 9 单元测试
错误处理很重要,但如果因为错误处理搞乱了代码,那它就是错误的代码
7.1 使用异常而非返回码遇到错误时,最好抛一个异常,这样调用代码会整洁,其逻辑不会被错误处理搞乱。
7.2 先写try-catch-finally异常的妙处之一是,他们在程序中定义了范围。执行try-catch-finally语句中的try部分的代码时,你是在表明可随时取消执行,并在catch语句中接续。
某种意义上,try代码块就像是事务。catch代码块将程序维持在一种持续状态,无论try中发生什么均如此。
所以在编写可能抛出异常的代码时,最好先写出try-catch-finally语句,这样能帮助你定义该代码的用户应该期待什么,无论try代码块中执行的代码出什么错都一样。
7.3 使用未检异常已检异常和未检异常:
- 未检异常(编译器要求必须处置的异常):
正确的程序在运行中,很容易出现的、情理可容的异常状况。除了Exception中的RuntimeException及RuntimeException的子类以外,其他的Exception类及其子类(例如:IOException和ClassNotFoundException)都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。- 已检异常(编译器不要求强制处置的异常):
包括运行时异常(RuntimeException与其子类)和错误(Error)。RuntimeException表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。
使用已检异常的代价就是违反开放/闭合原则。如果在方法中排除已检异常,而catch语句在3个层级之上,你就得在catch语句和抛出异常处之间的每个地方签名中声明该异常。(这意味着,对软件中较低层级的修改,都会波及较高层的签名)
7.4 给出异常发生的环境说明你抛出的每个异常,都应该提供足够的环境说明,以便判断错误的来源和位置。
在Java中,你可以从任何异常中得到栈踪迹,然而,栈踪迹无法告诉你改失败操作的初衷。
应该创建信息充分的错误信息,并和异常一起传递出去,在消息中,应包括失败的操作和失败类型。
对异常分类有很多方式。在定义异常类时,最重要的考虑应该是它们如何被捕获。
可以依据来源分类:是来自组件还是其他地方?也可以依据其类型分类:是设备错误、网络错误还是编程错误?
对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。
如果你想要捕获某个异常,并且放过其他异常,就使用不同的异常类。
7.6 定义常规流程你可以打包外部API以抛出自己的异常,在代码的顶端定义一个处理器来应对任何失败了的运算,也可以对特例场景使用特例模式。
特例模式:创建一个类或配置一个对象,用来处理特例,客户代码就不用应对异常行为了,异常行为被封装到特例对象中。
7.7 别返回null值要讨论错误,就要考虑到那些容易因其错误的做法。
第一项就是返回null值,返回null值,基本上就是在给自己增加工作量,也是在给调用者添乱。只要有一处没有检查null值,应用程序就会失控。
7.8 别传递null值在方法中返回null值是糟糕的做法,将null值传递给其他方法就更糟糕了。除非API要求你向它传递null值,否者就要尽可能避免传递null值。
在大多数编程语言中,没有良好的方法能应对由调用者意外传入的null值。事已至此,恰当的做法就是禁止传入null值。这样在编码的时候就会时时记住参数列表中的null值意味着出问题,从而大量避免这种无心之失。
7.9 小结整洁代码是可读的,但也要强固。可读与强固并不冲突,如果将错误处理隔离看待,独立与主要逻辑之外,就能写出强固而整洁的代码。做到这一步,我们就能单独处理它也可能级大地提升代码的可维护性。
8 边界 9 单元测试


