Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item 69-72 예외를 재대로 써보자! #37

Open
KIMSEI1124 opened this issue Jan 5, 2024 · 0 comments
Open

Item 69-72 예외를 재대로 써보자! #37

KIMSEI1124 opened this issue Jan 5, 2024 · 0 comments
Assignees

Comments

@KIMSEI1124
Copy link

KIMSEI1124 commented Jan 5, 2024

section: 10장 예외

  • 아이템 69
  • 아이템 70
  • 아이템 71
  • 아이템 72

🍵 서론

바로 본론으로 들어가보자!

🌒 본론

예외는 오직 예외 상황에서만 써야 한다.

예외는 이름이 말해주듯 오직 예외 상황에서만 사용 해야 합니다. 절대로 일상적인 제어 흐름용으로 쓰이면 안됩니다.
풀어서 설명하면 "표준적이고 쉽게 이해되는 관용구를 사용하고, 성능 개선을 목적으로 과하게 머리를 쓴 기법은 자제하라" 입니다.

1. 잘못된 예외 상황 예시


오직 예외 상황에서만 예외를 사용해야 하는데 언제 사용하지 않는지 잘 모를 수 있으니 코드로 알아보도록 하겠습니다.

try {
	int i = 0;
	while (true) {
		range[i++].climb();
	}
} catch (ArrayIndexOutOfBoundsException e) {

}

벌써부터 뭔가 잘못된 사용방법이라고 느껴지는데 다음과 같은 문제점 들이 있습니다.

  1. 위 코드를 표준적인 관용구대로 작성했다면 모든 자바 프로그래머가 곧바로 이해했을 것입니다. >> for문
  2. 예외를 사용한 쪽이 표준 관용구보다 훨씬 느리다. >> 성능상의 문제
  3. 제대로 동작하지 않을 수도 있다. >> 만약 반복문이 복잡하다면 디버깅하기 어렵다.

2. 잘 설계된 API 라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 합니다.


특정 상태에서만 호출할 수 있는 '상태 의존적' 메서드를 제공하는 클래스는 '상태 검사' 메서드도 함께 제공해야 합니다.

예를 들면 Iterator 인터페이스의 nexthasNext 가 각각 상태 의존 메서드와 상태 검사 메서드에 해당합니다.

3. 상태 검사 메서드 대신 사용할 수 있는 선택지


상태 검사 메서드

외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 옵셔널이나 특정 값을 사용합니다. 상태 검사 메서드와 상태 의존적 메서드 호출 사이에 객체의 상태가 변할 수 있기 때문입니다.

다른 모든 경우엔 상태 검사 메서드 방식이 조금 더 낫습니다. 가독성이 더 좋고, 잘못 사용했을 때 발견하기 쉽습니다.

옵셔널, 특정 값

성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행한다면 옵셔널이나 특정 값을 선택합니다.

public class OptionCase {  
    public void run() {  
	    Optional<Integer> optional = Optional.of(1);  
  
	    if (optional.isPresent()) {  // Optional 검사
		    System.out.println(optional.get());  
	    }  
    }  
}

public class ElseValueCase {  
	public void run() {  
	    Optional<Integer> optional = Optional.empty();  
  
	    if (optional.isPresent()) {  
		    System.out.println(optional.get());  
		    return;  
	    }  
  
	    System.out.println(optional.orElse(-1));  // 특정 값
    }  
}

70. 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라

1. 복구할 수 있는 상황이면 검사 예외를 던지자

호출하는 쪽에서 복구하리라 여겨지는 상황이라면 검사 예외(checked exception)를 사용한다.

💫 Info

검사 예외란? 호출자가 그 예외를 catch로 잡아 처리하거나 위로 전파하도록 throws 강제하는 것 입니다.

API 설계자는 API 사용자에게 검사 예외를 던져주어 그 상황에서 회복해내라고 요구한 것 입니다.

그러면 예제 코드를 한번 확인해보도록 하겠습니다. 아직 재대로 이해하지 못했으므로 뤼튼에서 제공하는 GPT-4를 이용하여 코드를 제공받았습니다.

public class CheckedExceptionExample {

    // 사용자 정의 검사 예외
    static class MyCheckedException extends Exception {
        public MyCheckedException(String message) {
            super(message);
        }
    }

    // 예외를 던질 수 있는 메소드 정의
    public void doSomethingThatMightThrowException() throws MyCheckedException {
        // 특정 조건이 충족됐을 때 예외를 던짐
        boolean condition = true; // 실제 코드에서는 실제 조건을 검사합니다.
        if (condition) {
            throw new MyCheckedException("이 조건이 참이면 예외가 발생합니다.");
        }
    }

    public static void main(String[] args) {
        CheckedExceptionExample example = new CheckedExceptionExample();
        try {
            example.doSomethingThatMightThrowException();
        } catch (MyCheckedException e) {
            // 예외 처리
            System.out.println("예외가 발생했습니다: " + e.getMessage());
        }
    }
}

위 코드에서의 핵심은 RuntimeException 을 사용하지 않고 Exception 을 사용한 것 입니다.

2. 프로그래밍 오류라면 비검사 예외를 던지자

런타임 예외의 대부분은 "클라이언트가 해당 API의 명세어 기록된 제약"을 만족하지 못했을 때 발생합니다.

// 이러한 상황이 일어나면 안되겠지만 예시로 들어봤습니다.
int[] array = new int[5];
System.out.println(array[5]);   // -> ArrayIndexOutOfBoundsException

복구할 수 있는 상황인지 프로그래밍 오류인지 항상 명확히 구분되는 것은 아니지만 말도 안되는 크기의 배열을 할당해 생긴 프로그래밍 오류일 수도 있고 진짜로 자원이 부족해서 발생한 문제일 수도 있습니다.

3. 에러

에러는 보통 JVM이 자원 부족, 불변식 깨짐 등 더 이상 수행을 계속할 수 없는 상황을 나타낼 때 사용합니다. 그래서 구현하는 비검사 throwable은 모두 RuntimeException 의 하위 클래스여야 합니다. Error는 상속하지도 말고, throw문도 던지지 않습니다.

AssertionError은 예외입니다!

71. 필요 없는 검사 예외 사용은 피하라

1. 프로그래머가 예외를 처리하도록 강제하고 싶을 때

  • 검사 예외는 흐름 제어의 한 형태로 사용될 수 있으나, 과도한 검사 예외는 코드를 읽고 쓰기 어렵게 만들 수 있습니다.
  • 예외를 처리하는 로직이 여러 층을 통과해야 한다면, 비검사 예외로 변환하거나, 대안적인 방법을 고려해야 합니다.

2. 검사 예외를 줄이는 방법

  • 예외 전환: 검사 예외를 잡아내고, 대신 비검사 예외를 던집니다. 이 방법은 예외를 처리할 수 있는 곳까지 예외를 전파하지 않아도 되는 장점이 있습니다.
  • 옵셔널 반환: Java 8부터는 Optional<T>를 사용하여 예외 대신 비어 있을 수 있는 결과를 반환할 수 있습니다. 이 방법은 클라이언트 코드가 결과의 존재 여부를 검사하게 하여 예외적인 상황을 처리할 수 있게 합니다.

72. 필요 없는 검사 예외 사용은 피하라

표준 예외를 재사용하면 얻는 게 많다

  1. 우리들이 작성한 API가 다른 사람이 익히고 사용하기 쉬워진다. 많은 프로그래에게 이미 익숙해진 규약을 그대로 따르기 때문입니다.
  2. 낯선 예외를 사용하지 않게 되어 읽기 쉽습니다.
  3. 예외 클래스 수가 적을수록 메모리 사용량도 줄고 클래스를 적재하는 시간도 적게 걸린다.

🍃 결론

  • 아이템69: 예외는 예외 상황에서 쓸 의도로 설계되었다.
  • 아이템70: 복구할 수 있는 상황이면 검사 예외를, 프로그래밍 오류라면 비검사 예외를 던지자!
  • 아이템71: 검사 예외를 사용하는 방법은 상황에 따라 신중하게 고려되어야 하며, API 사용자의 편의성과 코드의 명확성을 유지하는 것이 중요하다!
  • 아이템72: 표준 예외를 사용하자!

reference

  • Effective Java 3/E
  • 뤼튼
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant