[Java] try-with-resources 예외 처리

배상규·2023년 9월 11일
0
post-thumbnail

개요

프로젝트를 진행하면서 요약하자면 파일 다운로드 하는기능을 만들다가 오류가 발생했는데 디버깅 하기가 쉽지 않았다 그저 close()에서 오류가 발생했다고만 나오고 자세히 기술되어 나오지 않았기 때문이다. 대략적인 코드이다

  try  {
           File file = new File( "/test" );
           dataByte = new byte[(int) file.length()];
           fileInputStream = new FileInputStream(file);
           fileInputStream.read(dataByte);
        } catch (Exception e) {
           throw new UnknownException("Unknown Exception", e);
        } finally {
           fileInputStream.close();
        }

그래서 try-with-resources으로 바꾸었고 try-with-resources가 무엇인지 알아보자.


1. try-catch-finally란?

Java7 이전의 try-catch-finally

사용 후에 반납해주어야 하는 자운들은 Closable 인터페이스를 구현하고 있으며, 사용 후에 close 메소드를 호출해 주어야 했다.
앞선 코드에서는 파일을 다운 받는 경우로 예시를 들었었다.
문제는 이렇나 과정에서 여러가지 단점을 가지고 있다.

  • 자원 반납에 의해 코드가 복잡해진다.
  • 실수로 인한 자원반납 코드의 부재
  • 에러로 인한 자원 반납하지 못하는 경우
  • 디버깅의 어려움
이러한 문제를 해결하기 위해 try-with-resources라는 문법이 Java7부터 추가됐다.

Java7 이후 try-with-resources

Java는 이렇나 문제점을 해결하기 위해 자원을 자동 반납해주는 try-with-resources 문법을 추가했다. Java는 AutoCloseable 인터페이스를 구현하고 있는 자원에 대해 try-with-resources를 적용 가능하도록 만들었고, 이를 사용함으로써 코드의 유연함과 에러를 잡는 효과를 만들었다.

  try (FileInputStream fileInputStream = new FileInputStream(file)) {
      	   File file = new File( "/test" );
           dataByte = new byte[(int) file.length()];
           fileInputStream = new FileInputStream(file);
           fileInputStream.read(dataByte);
            }
        }

Closeable의 AutoCloseable 상속

close()를 따라가보면 기존의 Closeable에 부모 인터페이스로 AutoClosable을 추가한것을 볼 수 있다. 이는 기존 부모 인터페이스에 AutoClosable을 새롭게 추갛나 것이다.
보편적으로 먼저 만들어진 인터페이스를 자식 구현체에서 사용하는것이 일반적이다. 하지만 먼저 만들어진 Closable 인터페이스에 부모 AutoCloesable을 추가하면서 하위 호환성을 달성 시키며 변경 작업에 대한 수고를 덜었다. 만약 반대의 경우 Closeable을 부모로 만들고 진행하였다면 기존 상속받은 클래스들 모두 AutoClosable을 구현하도록 수정이 필요했을 것이다.


2. try-with-resources을 권장해야 하는 이유

에러 스택 트레이스의 누락

try-catch-finally를 이용하면 개요에서 설명한것 처럼 에러 스택 트레이스가 누락되는 경우가 발생할 수 있다. 이로인해 디버깅을 통한 오류 파악이 매우 어렵게 만든다.
예를 들어 AutoCloseable을 구현채를 만들어 보자

public class TestResource implements AutoCloseable {

    @Override
    public void close() throws RuntimeException{
        System.out.println("Test close");
        throw new Exception();
    }

    public void print() {
        System.out.println("print");
        throw new Exception();
    }
}

클래스를 살펴 보자면 print와 close 호출시 Exception을 발생시킨다. 이제 try-catch-finally와 try-with-resources로 테스트를 해보자

try-catch-finally로 구현

try-catch-finally로 자원을 반납하는 코드를 구현해보자

public void test() {
    TestResource testResource  = null;

    try {
        testResource = new TestResource();
        testResource.print();
    } finally {
       testResource.close();
    }
}

문제는 이코드를 실행하면 앞선 설명처럼 close 호출 시에 에러만 찍힌다는 것이다

try-with-resources로 구현

이번에는 try-with-resource로 구현해보자.

public void test() {
    try (TestResource testResource = new TestResource()) {
        testResource.print();
    }
}

이경우에는 print와 close 모두에 에러 트레이스가 남게 된다. 이런 에러 트레이스가 잘남는 다는것은 에러에대한 원인파악을 한층더 편하게 해준다. 또한 코드 자체도 finally로 close를 하지 않아도 자동으로 자원을 반납하기 때문에 close()를 실수로 호출하지 않아 생기는 문제도 미연에 방지할 수 있다.

정리하자면 다음과 같은 이유로 try-with-resources를 사용을 적극 권장한다.

  • 코드의 간결함
  • 번거로운 자원 반납 작업의 간소화
  • 모든 에러에 대한 스택 트레이스를 남김으로써 디버깅의 간소화

이러한 이유들로 앞으로는 자원을 반납하는 경우를 만나면 try-with-resources를 적극 사용하자.

profile
기록에 성장을

0개의 댓글