第七章 错误处理
- 错误处理不应该搞乱代码逻辑
7.1 使用异常而非返回码
- 在以往的错误返回中,通常使用返回码(宏定义)、NULL等;在这样的操作往往需要调用者在调用后立即进行错误处理;但此操作极为容易被忽略,存在隐患。
- 对于这种情况最好使用异常,隔离错误处理和业务逻辑;
7.2 先写Try-Catch-Finally
Try块和Catch块在程序中定义了一个范围;在Try块的代码中可以随时取消代码的执行,并在Catch块中继续;
在构建代码逻辑的过程中,可以一步步实现功能;
- 比如先写一个单元测试,实现单元功能;
@Test(expected = StorageException.class)//单元测试,检测实现抛出的异常 public void retrieveSectionShouldThrowOnInvalidFileName(){ sectionStore.retrieveSection("invalid-file"); } //部分实现功能,再在Try块中添加功能 public List<RecordedGrip> retrieveSection(String sectionName){ try{ FileInputStream stream = new FileInputStream(sectionName); stream.close(); }catch (FileNoFoundException e){ throw new StorageException("retrieval error",e); } return new ArrayList<RecordedGrip>(); }
7.3 使用不可控异常
- 所谓可控异常就是在C++中的异常范围;
- 不可控异常就是不使用异常范围;
- 异常范围会导致调用有异常的函数申明可能出现的异常,如果该异常的处理程序在多级上层,那么每一层级的函数都要声明可能要出现的异常;这违反了开放闭合原则;
- 对于一般应用的开发,可控异常(异常范围)成本要高于收益;
7.4 给出异常发生时的环境说明
- 当抛出一个异常时,需要给出产生异常的原因和位置,以便修改错误;
7.5 依调用者需要定义异常类
ACMEPort port = new ACMEPort(12);
//该段代码中包含了较多的重复,并且由各种异常,对此可以对第三方API进行打包,使其返回相同的代码;
try{
port.open();
}catch(DeviceResponseException e){
reportPortError(e);
logger.log("Device response exception",e);
}catch(ATM1212UnlockedException e){
reportPortError(e);
logger.log("Unlock exception",e);
}catch(GMXError e){
reportPortError(e);
logger.log("Device response exception",e);
}finally{
...
}
//以下方法对第三方API进行了打包,同一了向上throw的异常类型
LocalPort port = new LocalPort(12);
try{
port.open()
}catch(PortDeviceFailure e){
reportError(e);
logger.log(e.getMessage e);
}finally{
...
}
public class LocalPort{
private ACMEPort innerport;
public LocalPort(int portNumber){
innerPort = new ACMEPort(portNumber);
}
public void open(){
try{
innerPort.open();
}catch(DeviceResponseException e){
throw new PortDeviceFailure(e);
}catch(ATM1212UnlockExcetion e){
throw new PortDeviceFailure(e);
}catch(GMXError e){
throw new PortDeviceFailure(e);
}
}
...
}
- 对一个第三方的API进行打包,降低了对它的依赖;
7.6 定义常规流程
//此处打断了业务逻辑,消耗餐食和饭补是同一逻辑层的,将餐补放置异常处理,不符合逻辑
try{
MealExpense expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
}catch(MealExpenseNotFound e){
m_total += getMealPerDiem();
}
//此处对于餐补的情况返回一个特例情况,这样就不用处理异常情了;
MealExpensses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
public class PerDiemMealExpense implements MealExpenses{
public int getTotal(){
//retrun the per diem default
}
}
7.7 别返回null值
- 每一处可能返回null值就要进行返回值检查,这样的检查使得代码凌乱;
- 对于返回null的函数最好可以返回特例,避免null判断;次之返回异常进行处理;
7.8 别传递null值
- 除非API可以并且要求传递null值,否则不要;
- 对于java,空对象的使用对抛出NullPointerException的异常,对此,如果检测到有null,定义一个新的异常,并编写异常处理函数;