코드를 짜다보면 여기저기 오류처리 하는 부분이 발생하게 된다. 때문에 항상 고민해 왔던부분은 어떻게 예외를 처리해야 할까라는 고민이었다. 어디서 try-catch-finally를 해야하며, Exception 처리는 어디서 해야하는지, 커스텀 익샙션은 Checked로 만들어야 하는지 Unchecked로 만들어야 하는지…
일관성 없는 오류 처리는 코드의 가독성을 떨어뜨려 주고 프로그램의 논리를 이해하기에 어렵다. 즉, 깨끗한 코드를 만들려면 예외처리 하는부분을 반드시 신경써야 한다.
따라서 몇가지 고려사항을 통해 가독성 높은 코드를 작성해야한다.
오류코드보다 예외를 사용한다.
Try-Catch-Finally문 부터 작성한다.
unchecked 예외를 사용한다.(RuntimeException)
예외에 의미를 제공한다.
호출자를 고려해 예외 클래스를 정의한다.
외부 라이브러리를 호출하고 그 외부 라이브러리가 예외를 던진다면, 호출하는 라이브러리 API를 감싸서 예외 유형을 반환한다.(예외에 대응하는 방식이 거의 동일할때, 아래의 코드로 설명)
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 {
...
}
위의 방법은 예외 코드를 외부 라이브러리를 호출하는 try-catch문에서 전부 하나씩 처리함으로써 중복코드 발생
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);
}
}
...
}
아래의 코드는 예외를 처리하는 wrapper클래스를 통해 예외를 던짐으로써 외부 라이브러리 사이의 의존성 또한 줄일수 있게 된다. 또한 테스트를 함에 있어서도 쉬워진다.
정상 흐름을 정의한다.
예외를 통해 중단된 일을 처리하는건 대게는 좋은 방식이나, 때로는 이러한 방법이 적절하지 못할때가 존재한다. 아래의 예제를 통하여 알아보도록 하자.
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e) {
m_total += getMealPerDiem();
}
위의 코드는 예외가 논리를 따라가기에 어렵게 만든다. 따라서 아래에서 객체를 반환함으로써 예외가 발생하지 않게 코드를 변경할 수 있다.
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
public class PerDiemMealExpenses implements MealExpenses {
public int getTotal() {
// return the per diem default
}
}
SPECIAL CASE PATTERN : 클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식. 클래스나 객체가 예외적인 상황을 캡슐화해서 처리하기 때문에 클라이언트 코드가 예외적인 상황을 처리할 필요가 없다.
null을 반환하지 마라
null을 전달하지 마라