Note of CLEAN CODE chapter 7 - Error Handling

Error handling is important, but if it obscures logic, it’s wrong

Use Exceptions rather than return codes

In languages which do not support exceptions, you either set an error flag or return an error code that the caller could check like this

DeviceHandle handle = getHandle(DEV);
if(handle != DeviceHandle.INVALID){

}else{
    logger.log(errorMessage);
}

It clutters the caller. The caller must check for erros immediately after the call. But it is easy to froget, so it is better to throw an exception

public void sendShutDown(){
    try{
        tryShutDown();
    } catch(DeviceHandleException e){
        logger.log(e);
    }
}
    

private tryShutDown() throws DeviceHandleException {
    DeviceHandle handle = getHandle(DEV);
}

private DeviceHandle getHandle(DeviceId id){
    throw new DeviceHandleException(errorMessage);
}

Notice how much clearer it is. Two concerns that were tangled and now the device shut down and error handling are divided.

Write Your try-catch-finally Statement First

When you execute code in the try portion, you are stating that execution can abort at any point and the resume at the catch.

In this way, try blocks are like trasactions. Your catch has to leave your program in a consistent state, no matter what happens in the try

First thing first, we start with a unit test that shows that we’ll get an exception when the file does not exist

@Test(expected = StorageException.class)
public void testRetrive(){
    retriveSection("invalid file");
}

The test drives us to create the stub

public String retrieveSection(String sectionName){
    // dummy return until we have a real implemention
    return new String();
}

Our test fails because it does not throw an exception. Next it attempts to throw one

public String retrieveSection(String sectionName){
    try{
        FileInputStream stream = new FileInputStream(sectionName);
        stream.close();
    }catche(Exception e){
        throw new StorageException("retrive error ", e);
    }
    return new String();
}

Finally, narrow the type of exception we catch to match from FileInputStream constructor

public String retrieveSection(String sectionName){
    try{
        FileInputStream stream = new FileInputStream(sectionName);
        stream.close();
    }catche(FileNotFoundException e){
        throw new StorageException("retrive error ", e);
    }
    return new String();
}

Use Unchecked Exception

Checked excpetions (like IOExcpetion) are not necessary for the robust of productions.

Price of checked exceptions are Open/Closed Principle violation

If you throw a checked exception from a mathod and the catch is three levels above, you must declare that exception (add throws) in the signature of between you and the catch. A change in low level force signature changes on many higher levels.

This breaks the encapsulation, resulting in a cascade of changes that their way from the lowest levels of the software to the highest.

Provide Context with Exceptions

To determine the source and location of an error, you need to create informative error messages and pass them along with your exceptions.

Define Exception Classes in Terms of a Caller’s Needs

When we define exception classes in an application, our most important concern should be how they are caught.

ACEMport port = new ACMEport(12);
try{
    port.open();
} catch(DeviceResponseException e){
    reportPortError(e);
    logger.log("DeviceResponseException ", e);
} catch(GMXError e){
    reportPortError(e);
    logger.log("GMXError ", e);
} catch(OtherException e){
    reportPortError(e);
    logger.log("Others ", e);
} finally {
    ...
}

There is a lot of duplication. In most situations, the work is relatively standard regardless of the cause, record an error and make sure we can proceed

If all we do are similar regardless of the exception, we an simply fi them by wrapping the API we are calling and make sure it returns a common exception type

LocalPort port = new LocalPort(12);
try{
    port.open();
} catch(PortOpenError e){
    reportPortError(e);
    logger.log("DeviceResponseException ", e);
} finally {
    ...
}

and your localPort is a simple wrapper that catch and translates exceptions thrown by ACMPort

public class LocalPort {
    private ACMPort innerPort;
    public LocalPort(int portNumber){
        innerPort = new ACMEport(12);
    }

    public static void open(){
        try{
            port.open();
        } catch(DeviceResponseException e){
            throw new PortOpenError(e);
        } catch(GMXError e){
            throw new PortOpenError(e);
        } finally {
            ...
        }
    };
};

  • When you wrap a third-party API, you minimize your dependencies upon it
  • you are not tied to a particular vendor’s API design choices, you can define your own ones and write it more clearly

Define the Normal Flow

You define a handler above your code so that you can deal with any aborted computation. Sometimes you don’t want to abort

try{
    MealExpense expense = expenseDAO.getMeals(employee.getId());
    total += expense.getTotal();
}catch(MealExpenseNotFound e){
    total += getMealPerDiem();
}

The exception clutters the logic, it would be better if we did not have to deal with the special case

MealExpense expense = expenseDAO.getMeals(employee.getId());
total += expense.getTotal();
public class PerDiemMealExpense implements MealExpense{
    public getTotal(){
        //return meal expense per diem
    }
}

Don’t Return Null

If you work in code like this, it won’t be bad for you, but it is bad.

if(item != null){
    Registery reg = getRegister();
    if(reg != null){
        ...
    }
}

If we don’t check null, we will get a NullPointerException at a runtime, or someone catches it at a relative top level. Either of them is bad.

The problem might be “There is no null check in if statement”, but actually it has too many!

For example

List<Employee> employees = getEmployee();
if(employees != null){
    for(Employee employee : employees){
        ...
    }
}

Does it have to return a null? Why not write it as

List<Employee> employees = getEmployee();
for(Employee employee : employees){
    ...
}
public List<Employee> getEmployee(){
    if(/** no elements */){
        return Collections.emptyList();
    }
}

Don’t Pass Null

If you pass a null, you need to define a handler for InvalidArgumentException or assert, but it does not solve any problem!

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值