...
Exception Handling 3가지 기법
자바의 예외를 try - catch 블럭으로 잡았다고 해서 끝이 아니다.
예외가 발생하였으면 코드를 수정하여 문제점을 고쳐야 되는 것은 맞지만, 예상할 수 없는 예외인 경우 회피를 하거나 복구 동작을 하는 등 예외를 핸들링하여 처리하는 로직이 필요하다.
예상치 못한 예외가 발생하면 이를 단순히 catch문으로 잡아 에러 메세지를 출력하는 것을 떠나서, 실무에서 어떻게 예외를 효과적으로 처리하는지 방법에 대해 알아보도록 하자.
예외를 처리하는 방법에는 예외 복구, 예외 처리 회피, 예외 전환 방법이 있다.
1. 예외 복구
- 예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 방법
- Exception이 발생하여도 어플리케이션은 정상적으로 동작
- 반복문을 이용하여 예외가 발생하더라도 일정 수만큼 재시도를 하여 예외 복구를 시도
- 만일 최대 재시도 횟수를 넘기게 되는 경우 예외를 발생시키거나 혹은 다른 플랜으로 문제를 해결을 시도
- 네트워크에 연결하거나 하는 등의 로직에서 유용
final int MAX_RETRY = 100;
public Object someMethod() {
int maxRetry = MAX_RETRY;
while(maxRetry > 0) {
try {
// ...
return; // 성공시 바로 리턴
} catch(Exception e) {
// 예외 발생시 로그를 출력
} finally {
// 리소스 반납 및 정리 작업
}
--maxRetry; // 실패하면 1000번 반복
}
// 최대 재시도 횟수를 넘기면 직접 예외를 발생
throw new RetryFailedException();
}
2. 예외 처리 회피
- 예외 처리를 직접 담당하지 않고 호출한 쪽으로 던져 회피하는 방법 (throws)
- 다만 그렇게 추천 되어지는 방법은 아니다
- 호출한 쪽에서 예외를 처리하는 것이 바람직 할때 사용
- 해당 로직에서 예외가 발생했을 때 처리하지않고 회피하는 것이 최선의 방법일때만 사용
public void add() throws SQLException {
try {
// ... 생략
} catch(SQLException e) {
e.printStackTrace(); // 로그만 출력하고
throw e; // 다시 날린다
}
}
3. 예외 전환
- 위의 예외 처리 회피와 비슷하게 메서드 밖으로 예외를 던지지만, 그냥 무작정 던지지 않고 적절한 예외로 필터링해서 넘기는 방법
- 조금 더 명확한 의미로 전달되기 위해 적합한 의미를 가진 예외로 변경해서 throws 하는 것이라 보면 된다.
- 이외에도 예외 처리를 상위 클래스로 단순하게 퉁치기 위해 포장(wrap) 하는 방법도 일컫는다.
// 조금 더 명확한 예외로 던진다.
public void add(User user) throws DuplicateUserIdException, SQLException {
try {
// ...
} catch(SQLException e) { // SQLException 예외가 발생하면
if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) { // 그리고 정확히 어떠한 에러인걸 알았다면
throw DuplicateUserIdException(); // 상위 클래스가 아닌 정확한 예외클래스를 던진다
}
else {
throw e;
}
}
}
// 예외를 단순하게 포장한다.
public void someMethod() throws EJBException {
try {
// ...
}
catch(NamingException | SQLExceptionne | RemoteException e) { // 상세한 예외가 들어와도
throw new EJBException(e); // 상위 예외클래스로 퉁쳐서 포장해서 던진다
}
}
Exception Handling 주의 사항
1. catch에는 로깅, 복구 등의 로직을 추가하기
- 예외를 아무 로직 없이 catch만 하는 것은 바람직 하지 않다.
- 또한 catch에 단순히 throw만 하는 것도 바람직 하지 않다.
- 로그를 출력하거나, 문제를 원상 복구 시키는 로직을 첨가하는 등 catch만 수행하지 않고 해당 예외에 대한 처리를 해주어야 한다.
try {
// Exception 발생 가능 로직
} catch(Exception e) {
}
try {
// Exception 발생 가능 로직
} catch(Exception e) {
throw e;
}
2. 예외 Stack을 남겨 추적, 유지보수성 높이기
- Exception의 추적성과 유지보수 성을 높이기 위해서,
e.toString()이나e.getMessage()로 마지막 예외 메세지만 남기기보다, 전체 Exception Stack을 다 넘기는 편이 좋다. - 대표적인 slf4j 라이브러리의
log.error()역시e.printStackTrace()처럼 Exception의 stack도 남긴다.
3. Logging Framework 사용하기
e.printStackTrace()대신 LoggingFramwork(slf4j, commons logging, log4j, logback)를 활용하자.- Logging Framework를 이용하면 로그 파일을 쪼갤 수 있고, 여러 서버의 log를 한곳에서 모아서 보는 System을 활용할 수도 있다.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WelcomeWebController {
private final Logger log = LoggerFactory.getLogger(getClass());
public void welcomeWeb(ModelMap model) {
try {
// ...
} catch (ArrayIndexOutOfBoundsException ae) {
//System.out.println(ae + "배열의 길이를 확인해!");
log.info(ae + "배열의 길이를 확인해!");
} catch (NullPointerException ne) {
//System.out.println(ne + "null있다.");
log.info(ne + "null있다.");
} catch (Exception e) {
//System.out.println(e + "그 외 모든 오류들");
log.debug(e + "그 외 모든 오류들");
} finally {
//System.out.println("welcomeWeb Controller에서 오류");
//System.out.println("오류가 나던 안 나던 출력");
log.debug("welcomeWeb Controller에서 오류");
log.debug("오류가 나던 안 나던 출력");
}
}
}
# 참고자료
https://www.nextree.co.kr/p3239/
https://madplay.github.io/post/java-checked-unchecked-exceptions
인용한 부분에 있어 만일 누락된 출처가 있다면 반드시 알려주시면 감사하겠습니다
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.