Computer Science/Java

자바에서의 예외 처리

eunnnn 2023. 4. 16. 22:32

자바에서 에러, 예외와 관련 된 클래스의 계층구조는 위와 같다.

자바는 실행 시 (runtime) 발생할 수 있는 프로그램 오류를 에러(error)와 예외(exception)으로 구분한다.

 

에러와 예외가 어떻게 구분되는 지를 먼저 확인 해 보면,

예외는 입력 값에 대한 처리가 불가능하거나, 프로그램 실행 중에 참조된 값이 잘못된 경우 등 정상적인 프로그램의 흐름과 다르게 어긋나게 동작하는 것을 말한다. 이러한 예외는 개발자가 직접 처리할 수 있기 때문에 예외 상황을 미리 예측하여 핸들링한다.

그와 다르게 에러는 시스템 자체에 무엇인가 비정상적인 상황이 생긴 경우에 사용된다. 주로 자바 가상머신에서 발생 되며 예외와 다르게 애플리케이션 코드에서 잡을 수 없다. 예시로는 OutOfMemoryError, ThreadDeath, StackOverflowError 등이 있다.

 

 

예외 처리의 구분 - Checked Exception vs Unchecked Exception

예외는 Checked Exception과 Unchecked Exception으로 구분할 수 있다.

두 예외는 두 가지의 특징에서 구분된다.

 

1. RuntimeException을 상속하지 않은 클래스는 Checked Exception, 상속한 클래스는 Unchecked Exception으로 분류된다.

2. Unchecked Exception은 명시적인 예외처리가 필요하지 않고, Checked Exception은 명시적 예외처리가 필요하다.

@Test
public void throws_던지기() throws JsonProcessingException {
  final ObjectMapper objectMapper = new ObjectMapper();
  final Member member = new Member("yun");
  final String valueAsString = objectMapper.writeValueAsString(member);

}

@Test
public void try_catch_감싸기() {
  final ObjectMapper objectMapper = new ObjectMapper();
  final Member member = new Member("yun");
  final String valueAsString;
  try {
    valueAsString = objectMapper.writeValueAsString(member);
  } catch (JsonProcessingException e) {
    e.printStackTrace();
    throw new RuntimeException();
  }
}

위 JsonProcessingException는 IOException Exception을 상속하는 Checked Exception이다. 그렇기 때문에 throws로 상위 메서드로 넘기든 자신이 try catch 해서 throw를 던지든 해야 한다. 이것은 문법적으로 강제된다. 그에 반해 Unchecked Exception은 명시적인 예외 처리를 하지 않아도 된다.

 

그리고 추가적으로, Unchecked Exception과 Checked Exception의 구분점으로 흔하게 얘기되는 것이 Rollback 여부이다. 

흔히  Checked Exception 이면 롤백을 하지않고 그대로 진행되고, 만일 Unchecked Exception 이면 롤백을 한다고 얘기 되지만 , 이는 정확하게 말하면 틀린 설명이다.

예외처리에서의 롤백 여부는 전적으로 개발자의 마음이다. 자바의 유명한 프레임워크인 Spring Framework이 기본적으로 RuntimeException을 바로 롤백하게 되어있어, 이로부터 시작 된 오해다. 와중에 Spring Framework 또한  런타임 자식 예외클래스 중에 몇몇을 골라서 롤백을 하지 않도록 설정이 가능하다.

더욱 상세한 정보는 아래 링크를 참조하면 된다.

https://inpa.tistory.com/entry/JAVA-%E2%98%95-UnChecked-Exception%EA%B3%BC-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EC%9D%98-%EC%98%A4%ED%95%B4%EC%99%80-%EC%A7%84%EC%8B%A4

 

[JAVA] ☕ (Un)Checked Exception과 트랜잭션의 오해와 진실

잘못 알려진 (Un)Checked Exception 트랜잭션 Checked Exception 과 Unchecked Exception 차이를 알아볼때 다음과 같은 정리본을 발견 하였다. 만일 코드에서 예외가 발생했는데 Checked Exception 이면 롤백을 하지않

inpa.tistory.com

 

올바른 예외 처리 방식

1. 예외 복구

예외 상황을 파악하고 문제를 해결하여 정상 상태로 돌려놓는 방법이다.

public sendFile(String fileName)() {
        File file;
        try {
                file = FileFindService.find(fileName);
        } catch (FileNotFoundException e){ 
                // 기본 파일을 찾아서 전송한다.
                file = FileFindService.find("default.png");
        }

        send(file);
        }
}

그러나 해당 방식으로 예외를 복구 할 수 있는 상황은 당연하게도 거의 없다.

혹시나 예외 복구를 사용할 일이 생긴다면 위처럼 예외를 복구하는 방식 보다는, 코드의 흐름으로 예외 상황이 발생되지 않도록 제어하는 것이 좋다.

public void sendFile(String fileName){
        if(FileFindService.existed(filename)){
                // 파일이 있는 경우 해당 파일을 찾아서 전송한다.
                send(FileFindService.find(fileName));    
        }else{
                // 파일이 없는 경우 기본 이미지를 전송한다.
                send(FileFindService.find("default.png"));    
        }
}

 

2. 예외 처리 회피

public void method1(){
 try {
   method2(); // throws가 붙은 method2는 반드시 이렇게 try문 안에서 호출되어야 함.
}
 catch (ClassNotFoundException e1) {
  // method2가 떠넘긴 예외를 아래 catch문을 통해 처리해 주고 있다.
  System.out.println("클래스가 존재하지 않음.");
}

public void method2() throws ClassNotFoundException {
 Class clazz = Class.forName("java.lang.String22");
}

메소드 내부에서 예외가 발생할 수 있는 코드를 작성할 때, try-catch 블록으로 예외 처리 하는 것이 기본이지만, 경우에 따라서는 메소드를 호출한 곳으로 예외 처리를 떠넘길 수도 있다. 이 때 사용하는 키워드가 throws다.

만일 method1도 예외를 떠넘긴다면,  method1을 호출한 main 메소드에서 try-catch를 통해 예외 처리를 해 주어야 할 것이다. 그러나 main() 메소드에서도 예외를 떠넘기게 된다면 그 예외는 JVM이 최종적으로 처리를 하게된다. 이는 당연히 권장사항이 아니다.

 

3. 예외 전환

public Object method() {
        try {
                ...
        } catch (IOException e) {
                throw new CustomException ("IOException 발생");
        }
}

public class CustomException extends RuntimeException{
    public CustomException(String message) {
        super(message);
    }
}

예외 처리 회피와 비슷하게 메소드 밖으로 예외를 던지지만, 그냥 던지지 않고 적절한 예외로 전환해서 넘기는 방법이다. 명확한 의미로 전달되기 위해 적합한 의미를 가진 예외로 변경해야 한다. 보통 예외 처리를 하기 위해서 위와 같이 RuntimeException을 상속받은 클래스(UnChecked Exception)로 전환해 던진다.

 

4. 결론

  • 예외 복구 전략이 명확하고 복구가 가능하면 Checked Exception을 try-catch로 잡아서 예외 복구를 하거나, 코드의 흐름으로 제어하는 것이 좋다.
  • 그러나 이러한 경우는 흔하지 않기 때문에 Checked Exception이 발생하면 더 구체적인 UnChecked Exception을 발생시키고 예외에 대한 메시지를 명확하게 전달하는 것이 효과적이다.
  • 무책임하게 상위 메서드에 throw로 예외를 던지는 행위를 하지 않는 것이 좋다. 상위 메서드들의 책임이 그만큼 증가하기 때문이다.

 

출처

더보기