본문 바로가기
Web

[Java] Error와 Exception, 예외 처리

by DuncanKim 2022. 6. 29.
728x90

[Java] Error와 Exception, 예외 처리

 

추억의 블루스크린...

 

에러는 숙명, 예외는 운명

초보 개발자에게 누군가는 이렇게 이야기를 한다.

숙명은 static한 것, 운명은 dynamic 한 것이다.

어떠한 것을 집어넣는다고 해도, 숙명은 바뀌지 않고, 운명은 내가 어떤 변수와 로직을 넣느냐에 따라 좌지우지 할 수 있다.

 

오류는 시스템이 종료되어야 할 수준의 상황과 같이 수습할 수 없는 심각한 문제를 의미한다. 개발자가 예측하여 방치할 수 없는 것이다. 위의 블루스크린도 그와 같다.

 

반면 예외는 개발자가 구현한 로직에서 발생한 것이거나 사용자가 이렇게 사용할 것이라고 생각 못하고 개발해놓은 것에서 발생된다. 개발자가 미리 방지할 수 있기 때문에, 상황에 맞게 예외 '처리'를 해주어야 한다.

 

 

1. 예외 클래스의 계층 구조

 

https://t1.daumcdn.net/cfile/tistory/25168E45576C872633

 

 

오류와 예외 모두 자바의 최상위 클래스인 Object를 상속받는다. 그리고 그 사이에는 Throwable이 있다.

 

Throwable 클래스는 객체의 오류나 예외에 대한 메시지가 담긴다. 예외가 연결될 때, 연결된 예외의 정보들을 기록하기도 한다. Throwable 객체가 가진 정보와 할 수 있는 행위는 getMessage()와 printStackTrace()라는 메소드로 구현이 되어 있다. 이 메소드들을 활용해서 콘솔 상에 어떤 원인으로 예외 또는 오류가 발생하였는지를 표현할 수 있다.

 

 

 

2. 오류(Error)

 

개발자가 미리 오류를 대처하기는 힘들다. 아래에서는 위에 표시된 OutOfMemory, StackOverflow, Linkage 에러에 대해서만 간단히 알아볼 것이다.

 

  • OutOfMemoryError : 재귀 호출의 깊이가 깊어지거나 재귀가 계속되어서 스택에 메모리가 더 이상 남아있지 않을 때 나타나는 오류이다.
  • StackOverflowError : JVM이 할당된 메모리의 부족으로 더 이상 객체를 할당할 수 없을 때 나오는 오류. 가비지 컬렉터에 의해 추가 메모리가 확보되지 못하는 상황도 이에 속한다.
  • LinkageError : 클래스가 다른 클래스에 의존성을 가지고 있을 때, 무엇인가가 변경되어 클래스 파일에서 충돌이 일어나 발생하는 오류.

 

 

3. 예외(Exception)

 

개발자 수준에서 문제가 생기는 것이 예외라고 하였다.

예외에는 두 가지가 분류가 된다. 컴파일 단계에서 일어나는 예외와 런타임 과정에서 일어나는 런타임 예외로 나누어진다.

 

이런 두 가지의 분류를 다음과 같이 나타낼 수 있는데 CheckedException과 UncheckedExcetion 두 가지로 나타낼 수 있다.

 

CheckedException은 반드시 예외 처리를 해야 하는 것으로, 컴파일 단계에서 확인이 된다. 예외 발생을 할 때, 트랜잭션 Roll back(모든 것을 취소하고 이전의 상태로 되돌리는 것)이 되지 않는다. runtimeException을 제외한 모든 예외가 여기에 속한다. 컴파일 단계에서 이러한 예외가 생기면 실행조차 되지 않는다. IOException이 대표적이다.

 

UncheckedException은 런타임에서 발생하게 되는데, 보통 초보개발자들이 많이 볼 수 있는 OutofBound, NullPointerException 등이 이에 속한다. 실행을 하는 과정에서 오류를 일으킨다. 반복문을 돌리다가 배열의 크기보다 인덱스가 커졌는데도 값을 넣으려고 시도하는 것은 실행 중에 관찰할 수 있는 오류이다. 이렇듯 실행과정에서 일어나기 때문에 예외처리를 강제하지 않으며 트랜잭션 롤백이 가능하다.

 

 

 

4. 예외 처리

 

UncheckedException을 처리하는 모습을 한 번 보자.

public class Mathematics {
    public static void main(String[] args){
       int num1, num2;
       num1 = 12;
       num2 = 0;
       try {
            System.out.println(num1/num2);
       } catch (Exception e) {
            System.out.println("0으로는 값을 나눌 수가 없습니다.");
       }
    }
}

일단 try문의 코드를 실행해본 뒤에, 오류가 생기면 catch문 안에 있는 코드가 실행된다. 12를 0으로 나누었기 때문에,  ArithmeticException이 발생하고, catch문으로 넘어가서 문장이 출력된다. 만약 정상적으로 num2가 2라면, try문 안의 코드가 정상 실행 될 것이고, 6이 출력되는 것을 볼 수 있다.

 

 

예외처리를 하는 방법은 세 가지이다.

1) 예외 발생시 다른 작업으로 흐름으로 유도하는 예외 복구

2) 처리를 하지 않고 호출한 쪽으로 던지는 예외 처리 회피

3) 호출한 쪽으로 던질 때 명확한 의미를 전달하기 위해 다른 예외로 전환하여 던지는 예외 전환

 

1) 예외 복구

try{
	// 위험 코드
}catch(Exception e){
	// 오류 발생시 실행할 코드
}finally{
	// 리소스 반납 및 정리작업
}

예외가 발생하여도 정상적인 흐름으로 애플리케이션이 진행된다. 예외가 발생하면 그 예외를 잡어서 일정시간 만큼 대기하고 다시 재시도를 반복한다.

 

 

2) 예외 처리 회피

 

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

 

예외가 발생하면 throws를 통해 호출한 쪽으로 예외를 던지고 그 처리를 회피하는 것이다. 호출한 쪽에서 다시 예외를 받아 처리하도록 하거나, 해당 메소드에서 이 예외를 던지는 것이 최선이라는 확신이 있을 때만 사용해야 한다.

 

 

3) 예외 전환

 

catch(Exception e) {
	...
    throw DuplicatieUserIdException();
}

 

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

 

무분별한 throws의 활용은 코드 가독성을 떨어뜨리기 때문에 예외 전환 방법을 쓰기도 한다. 예를 들어 checked exception 중 복구가 불가능한 예외가 잡히면 이를 unchecked exception으로 전환하여 다른 계층에서 일일이 예외를 선언할 필요가 없도록 할 수 있다. 이렇게 되면 어떤 부분에서 예외가 발생했는 지 알기 쉬워지는 장점이 있다.

 

728x90

댓글