본문 바로가기

Spring

[Spring] 예외의 종류와 특징

Exception

Exception 클래스는 체크 예외와 언체크 예외로 구분되는데 체크 예외는 Exception 클래스의 서브클래스이면서 RuntimeException 클래스를 상속하지 않은 것들이고, 언체크 예외는 RuntimeException 클래스를 상속한 클래스들을 말한다.

 

체크 예외

일반적으로 예외라고 하면 RuntimeException 클래스를 상속 받지 않은 체크 예외를 의미하며 체크 예외가 발생할 수 있는 메소드를 사용할 경우 반드시 예외를 처리하는 코드를 함께 작성해 주어야 한다. 예외 처리를 해주지 않으면 컴파일 에러가 발생한다.

 

체크 예외의 예시

1. IOException - 파일 입출력시 파일이 없거나 다른 문제로 인해 파일이 읽히지가 않았을때 발생

2. SQLException - SQL 문법에 에러가 있거나 DB 접근 로직에 에러가 있을때 또는 서버가 죽거나 네트워크가 끊겼을때 발생

 

체크 예외의 장점

  • 개발자가 실수로 예외를 누락하지 않도록 컴파일 단계에서 에러를 잡아주는 안전장치이다.

체크 예외의 단점

  • Exception 클래스를 상속 받은 모든 체크 예외를 반드시 잡거나 던져줘야 하기 때문에 번거로울 수 있다.

언체크 예외

런타임 예외라고도 불리며 RuntimeException 클래스를 상속한 예외들은 명시적인 예외처리를 강제하지 않기 때문에 catch 문으로 잡거나 throws로 선언하지 않아도 된다. 선언하지 않을 경우 자동으로 예외를 던진다.

 

언체크 예외의 예시

1. NullPointerException - 오브젝트를 할당하지 않은 레퍼런스 변수를 사옹하려고 시도했을 때 발생

2. IllegalArgumentException - 허용되지 않는 값을 사용해서 메소드를 호출할 때 발생

 

언체크 예외의 장점

  • 신경쓰고 싶지 않은 예외를 무시할 수 있다.

언체크 예외의 단점

  • 컴파일 단계에서 에러를 잡아주지 않기 때문에 개발자가 실수로 예외를 누락할 수 있다.

예외처리 방법

예외 복구

첫번째로 예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 방법이 있다.

예외 복구는 예외가 어떤 식으로든 복구할 가능성이 있는 경우에 사용한다.

int maxretry = MAX_RETRY
int cnt = 0
while (cnt < MAX_RETRY) {
    cnt++
    try {
    	...    		// 예외가 발생할 가능성이 있는 시도
        return;		// 작업 성공
    } catch (SomeException e) {
    	// 로그 출력 후 정해진 시간만큼 대기
    } finally {
    	// 리소스 반납. 정리 작업
    }
}
throw new RetryFailedException(); // 최대 재시도 횟수를 넘기면 직접 예외 발생

예외처리 회피

두번째로 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 전달하는 방법이 있다.

예외를 회피하는 것은 예외를 복구하는 것처럼 의도가 분명해야 한다. 다른 오브젝트에게 예외처리 책임을 지게 하거나, 자신을 사용하는 쪽에서 예외를 다루는 게 최선의 방법이라는 확신이 있을때만 사용해야한다. 이러한 확신없이 무책임하게 회피만 하게 되면 결국 예외는 서버로까지 전달되고 말 것이다.

// 예외처리 회피 1
public void add() throws SQLException {
    ...
}
// 예외처리 회피 2
public void add() throws SQLException {
    try {
        ...
    } catch (SQLException e) {
        throw e;
    }
}

예외 전환

마지막으로 예외를 자신을 호출한 쪽으로 전달하는 예외처리 회피와 비슷하지만 발생한 예외를 그냥 넘기는게 아니라 적절한 예외로 전환해서 전달하는 방법이 있다.

 

예외 전환 사용 목적

1. 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못하는 경우, 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해

 

예를 들어 앞서 말했듯이 SQLException경우 문법 문제이거나 로직 문제 또는 서버쪽에 문제가 생겼을때도 발생할 수 있는데 이 모든 경우에 단순히 SQLException으로 throw를 하면 개발자 입장에서는 원인을 찾기가 쉽지 않다. 그렇기 때문에 같은 SQLException이라도 원인을 세분화해서 다른 Exception으로 치환해 보내준다면 보다 쉽게 에러를 해결할 수 있게 된다.

 

2. 예외를 처리하기 쉽고 단순하게 만들기 위해

주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.

// 회원가입시 id가 중복되어 에러가 발생한 경우
public void add(User user) throws DuplicateUserIdException, SQLException {
    try {
    	...
    } catch (SQLException e) {
    	// ErrorCode가 MySQL의 "Duplicate Entry(1062)"이면 예외 전환
        // ErrorCode는 데이터베이스 종류에 따라 다름
        if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) {
        	throw DuplicateUserIdException();
        } else {
        	throw e; // 그 외의 경우는 SQlException 그대로 전달
        }
    }
}