JaeWon's Devlog
article thumbnail
반응형

1. @SneakyThrows

- Java에서 메서드 선언부에 Throws 를 정의하지 않고도, 검사 된 예외를 Throw 할 수 있도록 하는 Lombok 에서 제공하는 어노테이션입니다.

즉, throws 나 try-catch 구문을 통해서 Exception 에 대해 번거롭게 명시적으로 예외 처리를 해줘야 하는 경우에 @SneakyThrows 어노테이션을 사용하여 명시적인 예외 처리를 생략할 수 있습니다.

- 예외 클래스를 파라미터로 입력받아 원하는 예외 클래스만 동작하도록 할 수 있습니다.

- JVM(클래스파일) 수준에서 검사 여부에 관계없이 모든 예외에 대해 throw 가 동작합니다.

- 룸북의 공식 홈페이지에서는 이 어노테이션은 논쟁의 여지가 있어 사용 시 신중하게 사용해야 한다고 말하고 있습니다.

2. 사용 이유

- 기존 Java에서 개발할 때 메소드 선언부에 throw 를 작성하여 예외처리를 하는 원칙을 무시하는 이상한 어노테이션이라고 생각될 수 있지만, 몇몇 특수한 경우에는 유용하게 사용될 수 있습니다.

- 아래 2가지는 룸북 공식 홈페이지에서 예시로 소개하고 있는 경우입니다.

2-1. Runnable

- Runnable 의 run() 메소드 안에서 발생한 예외는 명확하게 어떤 예외가 발생했는지 알기 어렵습니다.

- run() 메소드 안에서 발생한 예외는 모두 RuntimeException 으로 묶여 던져지기 때문입니다.

public class SneakyThrowsExample implements Runnable {

    @SneakyThrows(UnsupportedEncodingException.class)
    public String utf8ToString(byte[] bytes) {
    	return new String(bytes, "UTF-8");
    }
  
    @SneakyThrows
    public void run() {
        throw new Throwable();
    }
}

2-2. 발생할 수 없는 예외

- 예를들어, new String(bytes, "UTF-8"); 에서는 지원되지 않은 인코딩 타입에 대해서는 UnsupportedEncodingException 이 발생할 수 있다고 선언되어 있습니다.

- 하지만, JVM 에서는 UTF-8 이 항상 사용 가능해야 하기 때문에 아래 코드에서는 예외가 발생할 수 없습니다.

public static String utf8ToString(byte[] bytes) throws UnsupportedEncodingException{
    return new String(bytes, "UTF-8");
}

@SneakyThrows(UnsupportedEncodingException.class)
public static String utf8ToStringWithLombok(byte[] bytes) {
    return new String(bytes, "UTF-8");
}

3. 간단한 예제

3-1. 사용 이유

- 위에서 설명한 것처럼 Java 에서는 확인된 예외(Ex: IOException)에 대해서는 선언하거나 처리해야 하지만 @SneakyThrows 를 사용하면 해당 규칙을 우회할 수 있습니다.

- 아래 코드는 IOException 을 발생시키는 상황입니다.

//	컴파일 되지 않음.
public void sneakyThrowsCheckedAndSkips() {
    throw new IOException("Checked exception");
}

- 해당 코드를 컴파일을 하고자 하면 컴파일이 실패하면서 IOException 을 선언 또는 처리하라고 알려줍니다.

- 그렇기에 해당 오류에 대해 정상적인 코드는 메소드에 IOException 을 선언하거나 try-catch 문으로 처리해야만 합니다.

//	메소드에 IOException 선언
public void sneakyThrowsCheckedAndSkips() throws IOException {
    throw new IOException("Checked exception");
}
//	try-catch 처리
public void sneakyThrowsCheckedAndSkips() {
    try {
        throw new IOException("Checked exception");
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

- 반대로 확인되지 않은 예외(Ex: RuntimeException, NullPointerException)에는 선언 또는 try-catch 처리가 필요 없습니다.

//	정상 컴파일 동작.
public void throwsUncheckedAndSkips() {
    throw new RuntimeException("Unchecked exception");
}

- @SneakyThrows 어노테이션은 확인된 예외에서도 선언 또는 try-catch 처리를 하지 않도록 합니다.

@SneakyThrows
public void sneakyThrowsCheckedAndSkips() {
    throw new IOException("Checked exception");
}

- 해당 어노테이션을 통해 Lombok은 throw 된 확인된 예외를 래핑하거나 교체하지는 않지만 컴파일러가 확인되지 않은 예외라고 생각하도록 만듭니다.

- 하지만, 실제로 컴파일을 하고 .class 파일을 확인해보면 try-catch로 처리하는 것을 확인할 수 있습니다.

//	컴파일된 .class 파일
public void sneakyThrowsCheckedAndSkips() {
    try {
        throw new IOException("Checked exception");
    } catch (Throwable var2) {
        throw var2;
    }
}

- 즉, 일반적으로 확인된 예외를 잡아 RuntimeException을 내부에 래핑하여 던지는 작업을 @SneakyThrows 가 대신 통째로 해버리면서, 결과적으로 코드의 수를 줄여주는 역할을 하는 것입니다.

3-2. 사용을 권장하지 않는 이유???

- @SneakyThrows 를 사용하였을 때, 해당 메소드를 호출하는 곳에서는 몰래 던진 체크 예외를 직접 잡을 수가 없습니다.

- Java 컴파일러가 호출된 메소드 중 하나에서 throw 된 것으로 선언된 예외 유형을 예상하기 때문입니다.

- 위에 선언한 sneakyThrowsCheckedAndSkips 메소드를 다른 메소드에서 호출하고자 한다면 다음과 같이 메시지가 나옵니다.

public void test(){
    try{
        sneakyThrowsCheckedAndSkips();
    }catch (IOException e){
        // Exception 'java.io.IOException' is never thrown in the corresponding try block
        System.err.println(e.getMessage());
    }
}

@SneakyThrows
public void sneakyThrowsCheckedAndSkips() {
    throw new IOException("Checked exception");
}

- 간단하게 번역하자면, 컴파일러는 "Java.io.IOException 이 해당하는 try block에서 절대 발생하지 않습니다." 라고 합니다.

- 즉, sneakyThrowsCheckedAndSkips 메소드가 IOException을 발생시키고 있더라도 컴파일러는 이를 인식하지 못하여, 컴파일에 실패하게 됩니다.

- 해당 문제에 대해 해결하는 방법은 상위 유형인 Exception으로 잡아 처리할 수 있습니다.

public void test(){
    try{
        sneakyThrowsCheckedAndSkips();
    }catch (Exception e){
        System.err.println(e.getMessage());
    }
}

@SneakyThrows
public void sneakyThrowsCheckedAndSkips() {
    throw new IOException("Checked exception");
}
결론적으로, 
평소에는 확인된 예외에 대해서 알맞게 분기를 하여 처리하는데, @SneakyThrows를 사용하면 이러한 작업이 힘들기 때문에 개발 시에 권장하지 않는 것 같습니다.

참고

- https://projectlombok.org/features/SneakyThrows

- http://www.javabyexamples.com/lombok-sneakythrows

- https://partnerjun.tistory.com/55

- https://recordsoflife.tistory.com/621

반응형
profile

JaeWon's Devlog

@Wonol

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!