9장. 예외처리

4 minute read

목표

자바의 예외 처리에 대해 학습하세요. issue

학습할 것

자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

try-catch

public static void main(String args[]) throws IOException {
    FileInputStream is = null;
    BufferedInputStream bis = null;
    try {
        is = new FileInputStream("file.txt");
        bis = new BufferedInputStream(is);
        int data = -1;
        while((data = bis.read()) != -1){
            System.out.print((char) data);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // close resources
        if (is != null) is.close();
        if (bis != null) bis.close();
    }
}
  • 쓸데없는 코드가 계속 늘어남.
  • finally 라는 키워드를 사용해서 리소스를 닫아줘야 됨.

try-with-resource

  • finally 로 해당 변수를 닫아주는 부분을 보완해준다.

  • try를 선언할 때 함수 파라미터처럼 해당 변수를 선언한다.

    public static void main(String args[]) {
      try (
          FileInputStream is = new FileInputStream("file.txt");
          BufferedInputStream bis = new BufferedInputStream(is)
      ) {
          int data = -1;
          while ((data = bis.read()) != -1) {
              System.out.print((char) data);
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
    }
    

    코드가 매우 줄어들고 해당 리소스를 실수로 닫지 않는 경우가 존재하지 않는다.

하지만 해당 방법을 사용하기 위해서는 AutoCloseable을 구현한 객체만이 가능하다.

throws

public static void main(String args[]) throws IOException {
    FileInputStream is = new FileInputStream("file.txt");
    BufferedInputStream bis = new BufferedInputStream(is);

    int data = -1;
    while((data = bis.read()) != -1){
        System.out.print((char) data);
    }

    is.close();
    bis.close();
}

해당 코드에서는 try-catch 없이 해당 코드를 작성할 수 있다.

예외가 발생하는 메소드에 throws 라는 키워드를 통해서 해당 exception을 상위 메소드로 전가할 수 있다. 한마디로 내가 처리하지 못하거나 내가 처리하는 역할을 하지 못하는 경우 진행한다.

throw

public static void main(String args[]) throws IOException {
    FileInputStream is = null;
    BufferedInputStream bis = null;
    try {
        is = new FileInputStream("file.txt");
        bis = new BufferedInputStream(is);
        int data = -1;
        while((data = bis.read()) != -1){
            System.out.print((char) data);
        }
    } catch (IOException e) {
        throw new MyCustomException(e);
    } finally {
        // close resources
        if (is != null) is.close();
        if (bis != null) bis.close();
    }
}

catch statement가 달렸다. 내가 원하는 exception을 만들어서 던질수가 있다.

IOException이지만 이거는 MyCustomException가 더욱 적합하다고 하는 내가 원하는 것을 던질 수 있다고 생각하면 된다.

Ref

https://codechacha.com/ko/java-try-with-resources/ 에 존재하는 많은 내용을 참조했습니다.

자바가 제공하는 예외 계층 구조

자바_예외_구조

Exception과 Error의 차이는?

Error

런타임에서 실행 시 발생되며 전부 예측 불가능한 경우에 속한다.

StackOverflowError, OutOfMemoryError 같은 것들이 속한다.

Exception

Exception은 개발자가 구현한 로직에서 발생한다. 어떤 로직을 사용하는 것에 따라서 exception은 예측 가능할 수도 있고 예측 불가능할 수 있다.

모든 경우 try-catch와 같은 것으로 한다면 exception을 다룰 수 있다. 하지만 그렇게 하지 않는게… ㅎ.ㅎ

RuntimeException과 RE가 아닌 것의 차이는?

Not RuntimeException (= Checked Exception)

기본적으로 컴파일 단계에서 해당 예외를 처리해야 한다. 그렇지 않은 경우 컴파일 되지 않는다.

컴파일 단계에서 처리하라고 명시하기 때문에 해당 부분에서 문제가 발생했을 경우 트랜잭션 처리 부분에서 따로 롤백이 되지는 않는다.

대표적으로는 IOException, SQLException이 있다.

RuntimeException

우리가 다룰 수 없다는 과정을 기반으로 두고 있다고 생각한다. 일단 런타임을 진행하면서 발생하는 것이기 때문에 프로그래밍을 하면서 잡을 수도 있지만 잡지 못하는 경우도 있다.

그러기 때문에 컴파일 단계에서 따로 알림을 주지 않으며 (ide같은 경우 예측을 해주기는 한다.) 트랙잭션 중에 발생하는 경우 자동으로 롤백해준다.

대표적으로 NullPoint, IllegalArgument 등이 존재한다.

커스텀한 예외 만드는 방법

public class CustomException extents RuntimeException {
    public CustomException (String message) {
        super(message);
        // log.warn("warning!!!")
        // file.write(...)
    }
}

해당 exception에 대해서 선택적으로 상속을 받아서 구현한다.

위에 코드는 단순히 super을 통해서 부모 클래스의 구현을 그대로 따르지만 추가적인 작업을 할 수 있다. (log, file로 남긴다던지 가능하다)

예외를 처리하는 3가지 전략

예외 복구

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

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

예외처리 회피

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

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

예외 전환

catch(SQLException e) {  
   ...
   throw DuplicateUserIdException();
}

예외 전환은 위 [리스트 3]에서 처럼 예외를 잡아서 다른 예외를 던지는 것이다. 호출한 쪽에서 예외를 받아서 처리할 때 좀 더 명확하게 인지할 수 있도록 돕기 위한 방법이다. 어떤 예외인지 분명해야 처리가 수월해지기 때문이다. 예를 들어 Checked Exception 중 복구가 불가능한 예외가 잡혔다면 이를 Unchecked Exception으로 전환하여서 다른 계층에서 일일이 예외를 선언할 필요가 없도록 할 수도 있다.

나의 경우

많은 경우 예외 전환을 통해서 예외를 처리한다. 일단 RuntimeException으로 구현한 custom exception을 통해서 처리하도록 하고 그렇게 되면 일단 추가적으로 처리할 내용이 없다. (RuntimeException이기 때문에 처리가 쉬워진다.)

그리고 정확한 이유를 custom exception을 만들 때나 메시지에 적어서 작성하게 되면 나중에 코드를 읽거나 처리할 때 편한 것 같다.

Ref

https://codechacha.com/ko/java-try-with-resources/

https://cheese10yun.github.io/checked-exception/#checked-exception-3

https://www.nextree.co.kr/p3239/

라이브 방송

방송 중

10주차 과제

베스트 글 모음

Leave a comment