6. Error Handling - tuansondinh/clean_code GitHub Wiki
Don't use Return Codes. Reasons: "The caller must check for errors immediately after the call. Unfortunately, it’s easy to forget." S.104 Also we don't have clean separation between error handling and "normal" code.
BAD:
DeviceHandle handle = getHandle(DEV1); // Check the state of the device
if (handle != DeviceHandle.INVALID) {
// Save the device status to the record field
retrieveDeviceRecord(handle);
} else{
logger.log("Invalid handle for: " + DEV1.toString());
}
GOOD:
try {
getHandle(DEV1);
} catch (HandleError e) {
logger.log(e);
}
"This helps you define what the user of that code should expect, no matter what goes wrong with the code that is executed in the try." S.105
Don't use checked Exceptions. "The price of checked exceptions is an Open/Closed Principle violation. If you throw a checked exception from a method in your code and the catch is three levels above, you must declare that exception in the signature of each method between you and the catch. This means that a change at a low level of the software can force signature changes on many higher levels." S.107
"Each exception that you throw should provide enough context to determine the source and location of an error." S.107
- use informative error messages containing the operation that failed and type of failure
"... when we define exception classes in an application, our most important concern should be how they are caught." S.107
Following code contains a lot of duplication. (third-party library/API call) BAD:
ACMEPort port = new ACMEPort(12);
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");
} finally {
...
}
Use a wrapper class "...that catches and translates exceptions thrown by the ACMEPort class" S.108
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 (ATM1212UnlockedException e) {
throw new PortDeviceFailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
} ...
}
Using exceptions we have a nice separation between business logic and error handling. But be careful and avoid following mistake, where "...the exception clutters the logic." S.109
BAD
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e) {
m_total += getMealPerDiem();
}
Solution: Use the Special Case Pattern
"You create a class or configure an object so that it handles a special case for you. When you do, the client code doesn’t have to deal with exceptional behavior. That behavior is encapsulated in the special case object." S. 110
GOOD:
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
public class PerDiemMealExpenses implements MealExpenses {
public int getTotal() {
// return the per diem default
}
}
Now in this example getTotal()
will always return an object. If no meal -> object will return the per diem
If we return null, we need to always check for null. If we forget to check, the application can fail on us.
BAD:
List<Employee> employees = getEmployees();
if (employees != null) {
for(Employee e : employees) {
totalPay += e.getPay();
}
}
SOLUTION:
"If you are tempted to return null from a method, consider throwing an exception or returning a SPECIAL CASE object instead." S.110
List<Employee> employees = getEmployees();
for(Employee e : employees) {
totalPay += e.getPay();
}
public List<Employee> getEmployees() {
if( .. there are no employees .. )
return Collections.emptyList();
}
"Unless you are working with an API which expects you to pass null, you should avoid passing null in your code whenever possible." S.111 -> to avoid run time errors, exceptions
"Clean code is readable, but it must also be robust.[...] We can write robust clean code if we see error handling as a separate concern." S.112