코린이의 소소한 공부노트

연결된 예외 본문

Java

연결된 예외

무지맘 2022. 6. 10. 18:02

프로그램을 실행하는 과정에서 발생하는 미약한 오류를 예외라고 한다.

1. 예외는 사용자의 실수 등 프로그램 외적인 요소에 의해 발생하는 것으로, try-catch문을 이용해 발생한 예외를 처리한다.

2. 한 예외가 다른 예외를 발생시킬 수 있다. 예외 A가 예외 B를 발생시키면 A는 B의 원인 예외(cause exception)가 된다.

Throwable initCause(Throwable A) // A를 원인 예외로 등록
Throwable getCause() // 원인 예외를 반환

// Throwable 클래스 코드 살펴보기
public class Throwable implements Serializable{
    // ...
    
    // 원인 예외를 저장하기 위한 iv
    private Throwable cause = this; // 객체 자신을 원인 예외로 등록(초기화)
    
    // ...
    
    public synchronized Throwable initCause(Throwable cause){
        // ...
        
        this.cause = cause; // 매개변수 cause를 원인 예외로 등록
        return this;
    }
    
    // ...
}

 1) Throwable은 Exception과 Error의 조상이다.

 2) iv로 예외를 저장함으로써 예외 안에 또 다른 예외를 포함시키는 것이 연결된 예외인 것이다.

 

사용 예시를 들어보겠다. 아래 코드는 컴퓨터에 프로그램을 설치하는 과정에서 저장공간이 부족하게 됐을 때 원인 예외를 등록하는 과정을 나타낸 것이다.

void install() throws InstallException{
    try{
        startInstall();	// 저장 공간 부족 -> SpaceException 발생
        copyFiles();
    } catch(SpaceException e){ // e: 예외 A
        InstallException ie = new InstallException("설치 중 예외 발생"); // ie: 예외 B
        ie.initCause(e); // InstallException의 원인 예외로 SpaceException 지정
        throw ie;	// InstallException 발생시킴
    } catch(MemoryException me){
    // ...
}

 1) 메서드 선언부에는 InstallException(IE)이 선언되어있다.

 2) 메서드 실행 중 저장 공간이 부족해서 SpaceException(SE)가 발생하였다.

 3) catch문에서 IE 객체를 생성하고, 해당 객체에 SE를 원인 예외로 지정했다.

 4) IE를 발생시켰다.

이렇게 하면 실제로 발생한 예외는 SE지만, SE를 IE에 포함(연결)시킨 후 throw로 IE 객체를 던졌기 때문에 install() 메서드에서 발생한 예외는 IE가 되는 것이다. 그래서 메서드 선언부에 SE를 쓴 것이 아닌 IE를 쓴 것이다.

 

다소 복잡해 보이는데, 연결된 예외를 쓰는 이유는 무엇일까?

1. 여러 예외를 하나로 묶어서 다루기 위해

프로그램 설치 중 저장 공간이 부족, 메모리 부족 등 여러 예외가 발생할 수 있다. 예외 1개당 catch 블록을 1개씩 다 쓴다면, install() 메서드를 쓸 때마다 그 많은 catch 블록을 다 써야 한다. 연결된 예외를 쓰면 코드를 보다 간단하게 정리할 수 있다.

// 1) 연결된 예외 사용 전
try{
    install();
} catch(SpaceException e){
    e.printStackTrace();
} catch(MemoryException e){
    e.printStackTrace();
} catch( // ...

// 2) 연결된 예외 사용 후
try{
    install();
} catch(InstallException e){ // IE의 원인 예외로 SE, ME 등록
    e.printStackTrace();
} catch( // ...

// 사라진 SE, ME catch 블록을 install() 안에 넣어줘야한다.
void install() throws InstallException{ // IE 예외 선언
    try{
        startInstall();
        copyFiles();
    } catch(SpaceException e){
        InstallException ie = new InstallException("설치 중 예외 발생"); 
        ie.initCause(e);
        throw ie;
    } catch(MemoryException me){
        InstallException ie = new InstallException("설치 중 예외 발생");
        ie.initCause(me);
        throw ie;
    }
    // ...
}

위 코드에서는 실제로 발생한 SE, ME를 처리한 것이 아니라 예외를 연결해주기만 한 것이다. ie를 처리하는 부분은 다른 곳에 만들어줘야 한다.

이렇게 예외를 묶어서 다룰 경우 예외가 발생했을 때 나오는 메시지도 묶기 전과 다르다. 보다 정확한 예외 정보를 얻을 수 있다.

// 1) 연결 전 - 세부 정보 파악 불가
MemoryException: 메모리 공간이 부족합니다.	// 설치, 실행 중 언제 발생했는지 알 수 없음
	at Test.install(Test.java:22)
	at Test.main(Test.java:4)
    
// 2) 연결 후 - 대략적인 정보 + 세부 정보 파악 가능
InstallException: 설치 중 예외발생		// 발생 예외. 설치시 발생했다는 것을 파악할 수 있음
	at Test.install(Test.java:17)
	at Test.main(Test.java:4)
Caused by: MemoryException: 메모리 공간이 부족합니다. // 원인 예외
	at Test.startInstall(Test.java:31)
	at Test.install(Test.java:14)
	... 1 more

2. checked 예외를 unchecked 예외로 바꾸기 위해서 - 필수 처리를 선택 처리로 변경

필수 처리 예외가 많으면 try-catch문을 다 써야 하기 때문에 코드가 너무 길어져 불편해진다. 그리고 굳이 필수 처리를 하지 않아도 되는 것도 있는데 체크드 예외이기 때문에 무조건 try-catch를 써야 하는 경우도 생긴다. 이러한 불편함을 처리하기 위해 연결된 예외를 쓴다.

static void startInstall() throws SpaceException, MemoryException{
    if(!enoughSpace())
        throw new SpaceException("설치 공간 부족");
    if(!enoughMemory())
        throw new MemoryException("메모리 부족");
}

 1) startInstall() 메서드에 SE와 ME가 선언되어있다.

  - 이 두 예외는 체크드 예외(Exception 클래스의 자손)이다.

  - 언체크드 예외(RuntimeException 클래스의 자손)는 굳이 선언하지 않는다.

 2) 체크드 예외를 언체크드로 바꾸려면 클래스 선언부를 변경해야 한다.

class MemoryException extends Exception{
// ...

  - Exception을 RuntimeException로 바꾸면 언체크드 예외가 된다.

  - 하지만 ME는 이미 여러 곳에서 많이 쓰이기 때문에 변경할 수 없다.

  3) 클래스 선언부 변경 없이 체크드를 언체크드로 바꾸려면 연결된 예외를 이용하면 된다.

static void startInstall() throws SpaceException { // 변경부분 1
    if(!enoughSpace())
        throw new SpaceException("설치 공간 부족");
    if(!enoughMemory())
        throw new RuntimeException(new MemoryException("메모리 부족")); // 변경부분 2
}

  - ME가 원인 예외가 되어 RuntimeException을 일으킨다. -> ME가 선택 처리 예외가 되었다.

  - RuntimeException은 메서드에 예외 선언을 할 필요가 없다. -> 메서드 선언부에 SE만 선언한다.