티스토리 뷰

Java

[Java] 체크 예외(Check Exception)와 언체크 예외/런타임 예외 (Uncheck Exception, Runtime Exception)의 차이와 올바른 예외 처리 방법

망나니개발자 2021. 5. 13. 11:48
반응형

 

1. 체크 예외(Check Exception)와 언체크 예외/런타임 예외 (Uncheck Exception, Runtime Exception)의 차이


[ 예외(Exception)의 종류 ]

 

 

  • 에러(Error)
  • 예외(Exception)
    • 체크 예외(Check Exception)
    • 언체크 예외(Uncheck Exception)

 

 

에러(Error)

java.lang.Error 클래스의 하위 클래스들이다. Error는 메모리가 부족하는 등과 같이 시스템이 비정상적인 상황인 경우에 사용한다. 주로 JVM에서 발생시키기 때문에 애플리케이션 코드에서 잡아서는 안되며, 잡아서 대응할 수 있는 방법도 없다. 따라서 시스템 레벨에서 특별한 작업을 하는게 아니라면 이러한 에러 처리는 하지 않아도 된다.

 

 

 

예외(Exception)

java.lang.Exception 클래스와 하위 클래스들은 Error와 달리 애플리케이션 코드에서 예외가 발생하였을 경우에 사용된다. 그리고 Exception 은 다시 체크 예외와 언체크 예외로 구분된다.

 

 

 

체크 예외(Check Exception) 

체크 예외는 RuntimeException 클래스를 상속받지 않은 예외 클래스들이다. 체크 예외는 복구 가능성이 있는 예외이므로 반드시 예외를 처리하는 코드를 함께 작성해야 한다. 대표적으로 IOException, SQLException 등이 있으며, 예외를 처리하기 위해서는 catch 문으로 잡거나 throws를 통해 메소드 밖으로 던질 수 있다. 만약 예외를 처리하지 않으면 컴파일 에러가 발생한다.

 

 

체크 예외는 개발자가 실수로 예외 처리를 누락하지 않도록 컴파일러가 도와준다. 하지만 개발자가 모든 체크 예외를 처리해주어야 하므로 번거로우며, 신경쓰지 않고 싶은 예외까지 처리해야 한다는 단점이 있다.

또한 실제로 애플리케이션 개발에서 발생하는 예외들은 복구 불가능한 경우가 많다. 예를 들어 SQLExceptoin과 같은 체크 예외를 catch해도, 쿼리를 수정하여 재배포하지 않는 이상 복구되지 않는다. 그래서 실제 개발에서는 대부분 언체크 예외를 사용한다.

 

 

 

체크 예외(Uncheck Exception) 

RuntimeException 클래스를 상속받는 예외 클래스들은 복구 가능성이 없는 예외들이므로 컴파일러가 예외처리를 강제하지 않는다. 그래서  언체크 예외라고 불리는데, 언체크 예외는 Error와 마찬가지로 에러를 처리하지 않아도 컴파일 에러가 발생하지 않는다. 즉, 런타임 예외는 예상치 못했던 상황에서 발생하는 것이 아니므로 굳이 예외 처리를 강제하지 않는다. RuntimeException에는 대표적 NullPointerException이나 IllegalArgumentException 등과 같은 것들이 있다. 

 

 

언체크 예외는 신경쓰지 않고 싶은 언체크 얘외들을 모두 생략할 수 있다. 그래서 상당히 편리하지만, 컴파일러가 잡아주지 않으므로 개발자가 실수로 예외를 누락할 수 있다는 단점이 있다. 

위에서 설명한 체크 예외와 언체크 예외의 차이를 아는 것은 매우 중요하다. 왜냐하면 스프링 프레임워크가 제공하는 선언적 트랜잭션(@Transactional)안에서 에러 발생 시 체크 예외는 롤백이 되지 않고, 언체크 예외는 롤백이 되기 때문이다. 이는 자바 언어와는 무관하게 프레임워크의 기능임을 반드시 알고 넘어가도록 하자. (물론 옵션을 변경할 수 있다.)

 

 

 

 

[ 예외 처리 방법 ]

  • 예외 복구
  • 예외 처리 회피
  • 예외 전환

 

예외 복구

예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것이다. 만약 예외로 어떤 작업의 처리가 불가능하다면 다르게 작업을 처리하도록 유도함으로써 예외를 처리하는 방법이다.

대표적으로 다른 API 호출에 실패하였을 경우, 3회 정도 retry 하여 복구되도록 하는 것들이 있다.

 

 

예외 처리 회피

예외 처리를 직접 처리하지 않고, 자신을 호출한 곳으로 던져버리는 것이다. 만약 해당 예외를 처리하는 것이 자신이 해야될 일이 아니라고 느껴진다면 다른 메소드에서 처리하도록 넘겨줄 때 사용한다. 하지만 무작정 예외를 넘겨주는 것은 무책임한 회피가 될 수 있으므로 상황에 따라 적절하게 사용해야 한다.

 

 

예외 전환

예외 전환은 예외 회피와 마찬가지로 예외를 복구할 수 없는 상황에 사용되며, 예외처리 회피와 다르게 적절한 예외로 변환하여 던진다는 특징이 있다. 예외 전환은 크게 2가지 목적으로 사용된다.

  • 의미 있고 추상화된 예외로 바꾸는 경우
  • 런타임 예외로 포장하여 불필요한 처리를 줄여주는 경우

 

내부에서 발생한 예외를 그대로 던지는 것이 적절한 의미를 부여하지 못한다면 의미 있고 추상화된 예외로 바꾸는 것이 좋다.

예를 들어 새로운 사용자를 등록하고자 할 때 동일한 아이디가 존재하면 SQLException이 발생하게 된다. 하지만 이 에러를 그대로 던지면 이를 이용하는 서비스 계층에서는 왜 예외가 발생한건지 파악이 힘들다. 그래서 DuplicatedUserIdException과 같은 예외로 바꿔서 던지면 보다 확실히 의미를 전달할 수 있으며 상황에 따라 복구작업을 시도할 수도 있을 것이다.

또한 체크 예외에 의해 불필요하게 해주는 에러 처리가 많아진다면 이를 해결하기 위해 런타임 예외로 포장하여(언체크 예외로 변경하여) 불필요한 처리를 줄여줄 수 있다. 만약 복구하지 못할 예외라면 불필요하게 체크를 할 필요가 없기 때문이다. 그래서 이러한 경우라면 애플리케이션 로직 상에서 런타임 예외로 포장하여 던지고, 자세한 로그를 남기거나 알림을 주는 등의 방식으로 처리할 수 있다.

 

 

 

2. 올바른 예외 처리 방법


[ 올바른 예외 처리 방법 ]

1. 조치가 없는 try/catch -> 상황에 맞는 조치 진행

try/catch로 예외를 잡고 아무 조치도 하지 않는 것은 상당히 조심해야 한다. 예외가 발생하여도 무관하며, 다음 라인을 실행하겠다는 의도가 있는게 아니라면 반드시 피해야 한다.

이러한 코드는 오류가 있어서 예외가 발생했는데 그것을 무시하고 진행하는 꼴이므로 어떤 기능이 비정상적으로 동작하거나, 메모리나 리소스가 고갈되는 등의 문제를 야기할 수 있다. 그러므로 예외를 처리할 때에는 빈 값을 반환하는 등의 조치를 통해 상황을 적절하게 복구하거나 작업을 중단시키고 관리자에게 이를 전달해야 한다.

 

 

2. 무분별한 throws Exception -> 언체크 예외로 전환

무책임하게 throws Exception을 하는 것도 좋지 못하다. 왜냐하면 우리는 해당 문구를 보고 여기서 어떠한 문제가 발생할 수 있는지와 같은 의미있는 정보를 얻을 수 없기 때문이다. 또한 이 메소드를 다른 메소드에서 사용중이라면 throws Exception이 전파되므로 좋지 못하다.

public void method1() throws Exception {
    method2();
}

public void method2() throws Exception {
    method3();
}

public void method3() throws Exception {

}

만약 SQLException과 같이 복구가 불가능한 예외들이라면 기계적으로 throws를 던지지 않고 가능한 빨리 언체크/런타임 예외로 전환해주는 것이 좋다.

 

 

[ Spring의 예외 처리 예시 ]

Spring에서 데이터베이스에 접근하기 위한 @Repository 빈들에는 대표적으로 예외 전환 기법이 사용되고 있다. 훌륭한 오픈소스인 스프링 프레임워크를 통해 올바른 예외 처리를 살펴보도록 하자.

 

 

추상화된 예외로 전환

Spring 애플리케이션을 이용할 때 어떠한 데이터베이스가 사용될지 모른다. 만약 개발을 하다가 데이터베이스를 MySQL에서 PostgreSQL로 전환하는 경우에 에러 추상화가 없다면 모든 MySQL 에러를 PostgreSQL로 전환해야 할 것이다.

하지만 스프링의 @Repository에는 각기 다른 데이터베이스에 의한 에러를 Spring의 데이터베이스 에러인 DataAccessException로 전환해주는 기능이 있다. 이를 통해 개발자는 데이터베이스에 종속되지 않는 개발을 할 수 있다. 아래의 이미지를 보면 실제로 H2 데이터베이스에서 발생한 에러가 최종적으로 Spring 에러로 추상화되는 것을 확인할 수 있다.

 

 

 

언체크 예외로 전환

데이터베이스에 의한 에러는 일반적으로 체크 예외이다. 하지만 데이터베이스에 의한 체크 예외를 throws 받아도 우리가 특별히 할 작업은 따로 없고 에러 코드를 내려주는 것 뿐이다.

대표적으로 중복된 ID로 가입을 하는 경우를 생각해보도록 하자. 만약 중복된 ID로 가입을 시도하여 Constraint 에러가 발생하였다면 우리가 해야할 것은 올바른 에러 코드를 내려주는 것일뿐, 이는 체크 예외로 처리할 필요가 없는 것이다. 만약 체크 예외를 그대로 가져간다면 불필요하게 throw해주어야 하는 코드만 많아지는 것이다.

Spring은 이러한 문제를 해결하기 위해 예외를 언체크 예외로 정의하고 있으며, 이를 통해 우리는 무분별한 throw를 하지 않아도 되도록 도와준다.

 

 

 

Spring은 에러 전환 기법을 통해 DB에 무관하게 일관성있게 에러를 처리할 수 있도록 도와줄 뿐만 아니라 체크 예외를 언체크 예외로 전환하여 불필요한 에러 처리를 줄여주고 있다.

이러한 훌륭한 예시를 참고해서 올바른 에러 처리를 진행하도록 하자.

 

 

 

 

 

 

반응형
댓글
댓글쓰기 폼
반응형
공지사항
Total
3,301,914
Today
5
Yesterday
5,301
링크
TAG
more
«   2022/12   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함