代码整洁之道-读书笔记之错误处理
1. 使用异常而非返回码
在编码过程中,如果碰到错误的时候,建议抛一个异常
错误的例子
public class DeviceController{
public void sendShutDown(){
DeviceHandle handle=getHandle(DEV1);
//Check the state of the device
if (handle != DeviceHandle.INVALID){
// Save the device status to the record field
retrieveDeviceRecord(handle);
// If nat suspended,shut down
if (record.getStatus()!=DEVICE_SUSPENDED){
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}else{
logger.log("Device suspended. Unable to shut down");
}
}else{
logger.log("Invalid handle for: " +DEV1.tostring());
}
}
正确的处理
public class DeviceController{
public void sendShutDowm(){
try{
tryToShutDown();
} catch (DeviceShutDownError e){
logger.log(e);
}
private void tryToShutDown() throws DeviceShutDownError{
DeviceHandle handle =getHandle(DEV1);
retrieveDeviceRecord(handle);
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}
private DeviceHandle getHandle(DeviceID id){
throw new DeviceShutDownError("Invalid handle for:"+id.tostring());
}
}
2. 先写try-catch-finally语句
在某种意义上,try代码块就像是事务。catch 代码块将程序维持在一种持续状态,无论 try代码块中发生了什么均如此。所以,在编写可能抛出异常的代码时,最好先写出try—catch—finally语句。这能帮你定义代码的用户应该期待什么,无论try代码块中执行的代码出什么错都一样。
3.使用不可控异常
不可控异常也叫做检查性异常,就是方法进行throw的异常
以某个大型系统的调用层级为例。顶端函数调用它们之下的函数,逐级向下。假设某个位于最底层级的函数被修改为抛出一个异常。如果该异常是可控的,则函数签名就要添加throw 子句。这意味着每个调用该函数的函数都要修改,捕获新异常,或在其签名中添加合适的throw子句。以此类推。最终得到的就是一个从软件最底端贯穿到最高端的修改链!封装被打破了,因为在抛出路径中的每个函数都要去了解下一层级的异常细节。既然异常旨在让你能在较远处处理错误,可控异常以这种方式破坏封装简直就是一种耻辱。
如果你在编写一套关键代码库,则可控异常有时也会有用:你必须捕获异常。但对于一般的应用开发,其依赖成本要高于收益。
4. 给出异常发生的环境说明
应创建信息充分的错误消息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给catch块,并记录下来。
5. 调用者需要定义异常类
不同的异常定义不同的类
对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。如果你想要捕获某个异常,并且放过其他异常,就使用不同的异常类。
6. 定义常规流程
先看一段业务逻辑
如果消耗了餐食,则计入总额中,如果没有消耗,则员工得到当日的餐食补贴。
实现1:
try{
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
)catch(MealExpensesNotFound e){
m_total += getMealPerDiem();
}
实现2:
MealExpenses expenses=expenseReportDAO.getMeals(employee.getID());
m_total+=expenses.getTotal();
public class PerDiemMealExpenses implements MealExpenses{
public int getTotal(){
// 默认每日津贴
// return the per diem default
}
}
实现2的手法叫做特例模式(SPECIAL CASE PATTERN,[Fowler])。创建一个类或配置一个 对象,用来处理特例。你来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。
7. 别返回null值
方法的返回值尽量不要返回null值,好处是:避免NPE异常,调用方无需做非空判断,代码整洁
如果遇到了null,可以考虑:抛出异常、返回特例对象
8. 别传递null值
Integer -> int 拆箱
int -> Integer 装箱
方法的入参尽量不要传递null值,好处是:方法体无需进行非空判断,避免NPE异常,避免装箱、拆箱发生异常,代码整洁
9. 小结
即要保证代码可读性,也要保证代码的健壮性,异常逻辑切记要和正常逻辑进行隔离