예외 종류와 적절한 처리 전략들에 대해 정리해보았다. 프로그램 개발시, 적절한 복구 전략(try/catch)을 마련할 수 없을 경우, 언체크 예외로 전환하여 개발자에게 예외 상황을 보고하는 것이 안전하다.
Bad Case
다음은 안 좋은 예외 처리 방식들이다.
- try/catch로 예외를 잡고 아무것도 안하기. 예외를 무시하고 계속 진행하기 때문에, 개발자가 예외 발생 여부를 알지 못하게 된다.
try{
...
} catch(SQLException){
// 아무 처리 안하기
}
- 예외 메시지만 출력하기
try{
...
} catch(SQLException){
e.printStackTrace();
}
- 의미없고 무책임한 throws (호출한 메소드에 예외를 떠넘기기)
어떤 실행 중에 예외가 발생 했는지 알 수 없다.
public void method1() throws Exception {
method2();
}
public void method2() throws Exception {
method3();
}
public void method3() throws Exception {
⇒ 위와 같은 예외 처리 전략은 지양해야 하고, 예외 발생시 적절히 복구되거나, 진행을 중단하고 개발자에게 예외 상황을 보고해야 한다.
예외의 종류
- 에러(Error) : 자바 프로그램 밖에서 발생한 경우, 애플리케이션 코드에서 잡을 수 없!
- 예외(Exception)
- 체크 예외: 예외 처리 코드(try/catch)를 강제 ⇒ 프로그램이 try/catch에 따라 진행되고, 이후 정상 진행
- 언체크 예외, 런타임 예외 : 명시적으로 예외처리를 강제하지 않음 ⇒ 프로그램이 중단되고 예외 발생
예외 처리 방법
- 예외 복구
- 예외 처리 회피
-
예외 전환
- 중첩 예외
- 예외 포장
1. 예외 복구
예외가 발생하더라도, 애플리케이션은 정상 진행되어야 하는 상황에 예외 복구를 한다. 아래 코드에서 요청 파일을 읽으려고 하는데, 문제가 발생할 경우 일정 시간 대기하고, 다시 재시도한다. 이러한 재시도 횟수가 초과되면, 직접 예외를 throw한다.
int maxRetry=MAX_RETRY;
while(maxRetry-- > 0) {
try{
// 예외가 발생할 수 있는 코드
} catch(SomeException e){
// 정해진 시간만큼 대기
} finally {
}
}
throw new RetryFailedException(); // 재시도 횟수 초과시 직접 예외 발생
2. 예외 처리 회피
throws로 예외 처리를 자신을 호출한 메소드에게 위임한다. 하지만 이는 무책임한 책임 회피가 될 수 있다.
public void add() throws SQLException{
// ...
}
3. 예외 전환
예외 전환은 예외 회피(thows)처럼 예외를 밖으로 던지지만, 적절한 예외로 전환해서 던진다. (발생한 예외를 그대로 넘기지 않는다.) 예외 상황을 더 명확하게 알려줄 수 있는 예외로 변경하기 위해 사용한다.
try{
// ...
}catch(SQLException e){
if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) throw DuplicateUserIdException();
else throw e;
}
3-1. 중첩 예외
예외 전환을 통해 예외를 던지면 처음에 발생한 예외가 어떤 예외인지 확인 못 한다. 따라서 처음 예외를 알 수 있도록 중첩 예외를 통해 생성자 또는 initCause() 메소드를 통해 처음 발생한 예외를 담아서 던진다.
catch(SQLException e){
...
throw DuplicatedUserIdException(e); // 생성자
throw DuplicatedUserIdException().initCause(e); // initCause()
}
3-2. 예외 포장
예외 포장은 원래의 예외를 새로운 예외로 포장해서 던지는 것을 말한다. 그냥 던지는 게 의미가 없을 때 사용한다. 체크 예외를 런타임 예외로 변환하는 경우에 사용한다. 복구 가능한 예외가 아닌 경우 런타임 예외로 만들면 트랜잭션을 롤백하게 된다. 또한, 런타임 예외이므로 개발자에게 예외 상황을 보고하게 된다.
try{
OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
Order order = orderHome.findByPrimaryKey(Integer id);
} catch(NamingException ne){
throw new EJBException(ne); // 런타임 예외로 포장해서 던짐
}
4. 예외처리 전략
1. 체크 예외를 언체크 예외(런타임 예외)로 포장해서 던지자.
체크 예외로, 즉 try/catch 예외 복구로 해결할 수 없는 상황이 더 많다. 따라서 런타임 예외으로 변경하여 던지면 개발자에게 예외 상황을 보고 할 수 있어 좋다.
예를 들어, 아이디 중복 등으로 인한 SQLException은 try/catch문으로 예외 복구를 해줄 수 있다. 하지만, 대부분의 경우 try/catch로 복구 불가능한 예외이다. 따라서 어차피 적절한 예외 처리를 못하므로 런타임 예외로 변경하여 개발자에게 상황을 보고하는 것이 좋다.
2. 애플리케이션 예외
애플리케이션 예외는 프로그램 로직에 의해 의도적으로 발생시켜 try/catch로 처리하는 예외이다. 예를 들어 은행 잔고 부족일 경우, try/catch을 통해 사용자에게 관련된 안내 메세지를 보내야 한다.