Published on

[ JAVA ] Exception

Authors
  • avatar
    Name
    유사공대생
    Twitter

우아한 테크코스 프리코스를 진행하던 도중, 예외처리에 대한 오류가 발생해서 해결하려다가 그냥 처음부터 정리하려고 한다. 저번 프리코스에서는 구현에만 초점을 맞춰 프로그램을 작성했다면, 이번에는 OOP와 각 코드를 왜 작성하는지에 대해 중점적으로 보고 있다. 그래서 이번에는 자바의 예외처리가 어떻게 구성되어 있는지, 코드는 어떤 방식으로 작성해야 하는지 알아보려고 한다.

Error & Exception

오류(Error)는 시스템에 비정상적인 상황이 생겼을 때 발생한다. 이는 시스템 레벨에서 발생하기 때문에 심각한 수준의 오류이다. Error 클래스 및 하위 클래스들은 주로 가상머신(Virtual Machine)에서 발생하는 심각한 문제, 메모리 부족 등과 같은 상황을 나타낸다. 프로그램이 이러한 에러를 처리하려고 하지 않는 것이 일반적이고, 이러한 에러가 발생하면 프로그램이 종료된다.

따라서 개발자가 미리 예측해서 처리할 수 없기 때문에, 어플리케이션에서 오류에 대한 처리를 신경쓰지 않아도 된다.

오류가 시스템 레벨에서 발생한다면, 예외(Exception)은 개발자가 구현한 로직에서 발생한다. Exception 클래스 및 하위 클래스들은 프로그래머가 예외 상황을 처리하고 복구하는 데 사용된다. 즉, 예외는 개발자가 처리할 수 있기 때문에 예외를 구분하고 그에 따른 방법을 명확히 알고 적용하는 것이 중요하다.

에러(error): 프로그램 코드에 의해서 수습될 수 없는 심각한 오류 예외(exception): 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

예외 클래스의 구조

image

모든 예외클래스는 Throwable 클래스를 상속받고 있으며, Throwable은 최상위 클래스 Object의 자식 클래스이다.

Throwable을 상속받는 클래스는 Error, Exception 이 있다. Error는 시스템 레벨의 심각한 수준의 에러이기 때문에 시스템에 변화를 주어 문제를 처리해야 하는 경우가 일반적이다. 반면에 Exception은 개발자가 로직을 추가하여 처리할 수 있다.

Exception은 두 범주로 나눠서 볼 수 있는데 바로 Checked Exception인지, Unchecked Exception인지 이다.

Checked Exception && Unchecked Exception

image

Checked ExceptionUnchecked Exception
처리여부반드시 예외를 처리해야 함명시적인 예외를 강제하지 않음
확인 시점컴파일 단계실행 단계
예외 발생 시 트랜잭션 처리roll-back 하지 않음roll-back 함
대표 예외Exception의 상속받는 하위 클래스 중 Runtime Exception을 제외한 모든 예외Runtime Exception의 하위 예외

Checked Exception과 Unchecked Exception의 가장 명확한 구분 기분은 "꼭 처리를 해야 하느냐"이다. Checked Exception이 발생할 가능성이 있는 메소드라면 반드시 try/catch로 감싸거나 throw로 던져서 처리해야 한다. 반면에 Unchecked Exception은 명시적인 예외처리를 하지 않아도 된다.

Unchecked Exception 예외는 피할 수 있지만 개발자가 부주의해서 발생하는 경우가 대부분이고, 미리 예측하지 못했던 상황에서 발생하는 예외가 아니기 때문에 굳이 로직으로 처리를 할 필요가 없게 만들어져 있다.

또한 예외를 확인할 수 있는 시점에서도 구분할 수 있다. 일반적으로 컴파일 단계에서 명확하게 Exception 체크가 가능한 것을 Checked Exception이라 하며, 실행과정중 어떠한 특정한 논리에 의해 발견되는 Exception을 Unchecked Exception이라 한다. 따라서 컴파일 단계에서 확인할 수 없는 예외라 해서 Unchecked Exception이고, 실행과정중 발견된다 해서 Runtime Exception이라 한다.

그리고 예외발생시, 트랜잭션의 roll-back 여부도 인지하고 있으면 좋다. 기본적으로 Checked Exception은 예외가 발생하면 트랜잭션을 roll-back하지 않고 예외를 던져준다. 하지만 Unchecked Exception은 예외 발생 시 트랜잭션을 roll-back한다는 점에서 차이가 있다.

트랜잭션의 전파방식, 즉 어떻게 묶어놓느냐에 따라서 Checked Exception이냐 Unchecked Exception이냐의 영향도가 크다. roll-back 이 되는 범위가 달라지기 때문에 개발자가 이를 인지하지 못하면, 실행결과가 맞지 않거나 예상치 못한 예외가 발생할 수 있다. 그러므로 이를 인지하고 트랜잭션을 적용시킬때 전파방식과 롤백규칙 등을 적절히 활용하면 더욱 효율적인 애플리케이션을 구현할 수 있을 것이다.

printStackTrace()와 getMessage()

printStackTrace() 예외 발생 당시의 호출 스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다. getMessage() 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨 있으며, getMessage()printStackTrace()를 통해서 이 정보들을 얻을 수 있다.

예외처리 방식

1. 예외 복구

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

예외복구의 핵심은 예외가 발생해도 애플리케이션은 정상적인 흐름으로 진행된다는 것이다. 위 코드의 예제는 네트워크가 환경이 좋지 않아서 서버에 접속이 안되는 상황의 시스템에 적용하면 효율적이다.

2. 예외처리 회피

public void add() throws SQLException {
	// 구현 로직
}

위 코드는 간단해 보이지만 아주 신중해야 하는 로직이다. 예외가 발생하면 throws를 통해 호출한 쪽으로 예외를 던지고 그 처리를 회피하는 것이다. 하지만 무책임하게 던지는 것은 위험하다. 호출한 쪽에서 다시 예외를 받아 처리하도록 하거나, 해당 메소드에서 이 예외를 던지는 것이 최선의 방법이라는 확신이 있을 때만 사용해야 한다.

3. 예외 전환

catch(SQLException e) {
	throw DuplicateUserId();
}

예외전환은 위 코드처럼 예외를 잡아서 다른 예외를 던지는 것이다. 호출한 쪽에서 예외를 받아서 처리할 때 좀 더 명확하게 인지할 수 있도록 돕기 위한 방법이다. 어떤 예외인지 분명해야 처리가 수월해지기 때문이다.

예를 들어 Checked Exception 중 복구가 불가능한 예외가 잡혔다면 이를 Unchecked Exception 으로 전환하여서 다른 계층에서 일일히 예외를 선언할 필요가 없도록 할 수 있다.

참고