[Java] Exception 처리하기

SungBum Park·2019년 10월 20일
3

Checked Exception과 Unchecked Exception

  • Checked Exception: 컴파일 시간에 검사하는 예외로서, 처리하지 않으면 컴파일 에러가 나므로 반드시 처리해야 한다. 처리 방법에는 try/catch 로 감싸거나, throws 로 이를 호출한 메서드에게 예외를 넘겨준다.(이는 자바에만 존재하는 특별한 예외 처리 방식이다.)
  • Unchecked Exception: 런타임 시간에 검사하는 예외로서, 컴파일하는데 문제는 없지만 실행 중에 예외가 발생할 수 있다. 주로 프로그래머의 실수, 잘못 작성된 코드, 사용자가 애플리케이션을 잘못 사용할 때 이를 처리하는 경우에 발생한다.
Checked ExceptionUnchecked Exception
처리 여부반드시 예외 처리 해야함예외 처리 하지 않아도됨
트랜잭션 Rollback 여부Rollback 안됨 (반드시 해결해야하므로 롤백하거나 처리하거나 선택지를 준다.)Rollback 진행 (해결할 수 없으므로 롤백한다.)
대표 ExceptionIOException, SQLExceptionNullPointException, IllegalArgumentException

Java에서 Exception 처리하기

기존에 예외를 처리할 때는 log를 통해 예외의 메시지만 출력해주고 아무것도 해주지 않았다. 그리고 Checked Exception을 커스텀 런타임 예외(Unchecked Exception)으로 변경하여 던져서 처리하는 경우에도 기존의 Checked Exception에 대한 정보를 보존하지 않고 메시지만을 처리하곤 했다. 이러한 경우 다음과 같은 문제가 발생한다고 생각했다.

  • 예외를 추적하기 힘들다.(디버깅이 힘들다.)
  • 커스텀 런타임 예외로 변경하여 던지면 이를 호출한 곳에서 처리해야 하는데, 기존의 Exception의 정보가 필요한 경우가 생길 수 있다.

그러면 내가 기존에 한 방식에서 문제점을 각각 살펴보고 해결방법을 알아보자.

로그에 예외 정보 출력하기

private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);

public void update(String query) {
  try {
    // ...
  } catch (SQLException e) {
    log.error(e.getMessage());
  }
}

로그 시스템을 사용하여 예외를 처리할 때 문제의 코드이다. 만약 이 코드에서 SQLException이 발생하면 아래와 같은 메시지가 출력된다.

java_exception1.png

위와 같은 출력 결과로서는 어디서 에러가 발생한 것인지 전혀 알 수 없다. 예외가 발생하는 경우 표시된 stack trace를 통해 예외를 추적해야 하는데, 위 그림에서는 이를 전혀 출력하고 있지 않다. stack trace는 다음과 같은 그림을 말한다.

java_exception2.png

이 외에도 다음과 같은 경우 exception을 보존할 수 없다고 한다.

try {
  /* ... */
} catch (Exception e) {   // Noncompliant - exception is lost
  LOGGER.info("context");
}

try {
  /* ... */
} catch (Exception e) {  // Noncompliant - exception is lost (only message is preserved)
  LOGGER.info(e.getMessage());
}

// Noncompliant - exception is lost
try { /* ... */ } catch (Exception e) { throw new RuntimeException("context"); }

아래와 같은 코드는 예외 처리를 하지 않은 것과 같다.

try {
  // ...
} catch (Exception e) {

}

try {
  // ...
} catch (Exception e) {
  throw e;  
}

위에서 stack trace는 예외가 발생한 부분을 추적할 수 있는 지표라고 했다. 그래서 아래와 같이 예외를 처리하는 경우가 있는데, 이보다는 로깅 프레임워크를 사용하는 습관을 들이는 것이 좋다.(활용할 수 있는 곳이 많다.)

try {
  // ...
} catch (Exception e) {
  e.printStackTrace();
}

예외가 발생했을 때 stack trace를 표시하고 exception 정보를 보존하는 방법은 기본적으로 다음과 같다.

try { /* ... */ } catch (Exception e) { LOGGER.info(e); }

그리고 내가 적용한 방법은 다음과 같다.

private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);

public void update(String query) {
  try {
    // ...
  } catch (SQLException e) {
    log.error("Fail update :", e);
  }
}

SQLException을 매개변수로 넘겨주는 모습을 볼 수 있다. 위와 같이 사용하면 아래 그림과 같은 출력 결과를 볼 수 있다.

java_exception3.png

이제는 stack trace를 볼 수 있어 쉽게 어디서 예외가 발생했는지 알 수 있다.

Custom RuntimeException으로 던지기

예외를 처리할 때 로그를 활용하는 것 이외에 checked exception을 unchecked exception으로 변경해서 던져야 하는 경우가 있었다. 이를 자바에서는 RuntimeException 클래스를 상속받아서 할 수 있다. 이를 처리할 때도 기존의 예외를 보존해야 한다.

먼저, 기존의 문제 코드를 보자.

public void update(String query) {
  try {
    // ...
  } catch (SQLException e) {
    throw new DataAccessException();
  }
}

위 코드는 checked exception인 SQLException을 직접 구현한 런타임 예외 클래스인 DataAccessException으로 바꿔서 던져주는 모습이다. 하지만 생성자로 아무것도 받지 않으므로 기존의 SQLException 정보를 보존할 수 없다. 위 코드에서 SQLException이 발생하면 아래와 같은 결과가 나온다.

java_exception4.png

이를 해결하는 방법으로 RuntimeException을 상속받아서 생성해 줄 때 Throwable 객체(RuntimeException 상위 클래스)를 생성자에 넣어줄 수 있다. 즉, catch한 exception을 생성자로 받아줄 수 있다.

public RuntimeException(String message, Throwable cause) {
        super(message, cause);
}

물론 RuntimeException을 생성할 때 Throwable 만을 받을 수 있는 생성자 등 여러 생성자를 제공해준다. 위 생성자를 사용한 이유는 따로 메시지를 출력해주고 싶었기 때문이다.

public void update(String query) {
  try {
    // ...
  } catch (SQLException e) {
    throw new DataAccessException(e);
  }
}

java_exception5.png

위와 같이 사용하면 직접 만든 메시지를 출력할 수 있고, 가장 마지막을 보면 SQLException이 어디서 발생했는지까지 표시해준다.

참고자료

profile
https://parker1609.github.io/ 블로그 이전

0개의 댓글