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 73-77. 예외를 사용하는 방법 #33

Open
kgh2120 opened this issue Jan 4, 2024 · 0 comments
Open

Item 73-77. 예외를 사용하는 방법 #33

kgh2120 opened this issue Jan 4, 2024 · 0 comments
Assignees

Comments

@kgh2120
Copy link
Member

kgh2120 commented Jan 4, 2024

section: 10장 예외

아이템 73 추상화 수준에 맞는 예외를 던져라
아이템 74 메서드가 던지는 모든 예외를 문서화하라
아이템 75 예외의 상세 메세지에 실패 관련 정보를 담아라
아이템 76 가능한 한 실패 원자적으로 만들라
아이템 77 예외를 무시하지 말라


🍵 서론

프로그램을 개발할 때 보면 예외 상황이 굉장히 많다. 사실 프로그래밍은 정상적으로 흘러가는 경우를 개발하는 것이 아닌, 어떻게 하면 깨질 수 있는지를 찾아내는 작업인 것 같다. 깨져버리는 그 상황을 찾아냈다면, 우리가 만든 코드를 사용하는 사람들에게 적절한 경고를 해줘야 할 것이다. 이번 섹션인 10장 예외에서는 예외를 우리가 어떻게 사용해야 하는지에 대해서 다룬다. 책의 내용을 다양한 예시와 함께 알아보자.

🌒 본론

아이템 73 추상화 수준에 맞는 예외를 던져라

책에 나온 예시인 AbstractSequencetialList를 살펴보자. AbstractSequencetialList는 내부에서 ListIterator를 이용하기 때문에, NoSuchElementException이 발생하기 때문이다. 그리고 이를 캐치해서 IndexOutOfBoundsException 예외를 던져준다.

    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

IndexOutOfBoundsException를 던져주는걸까? 이는 AbstractSequencetialList의 계층구조를 살펴보고 생각해보자.

이 그림을 살펴보면 AbstractSequentialList를 구현한 구조가 어떤 형태인지 예상이 갈 것이다.

NoSuchElementException라는 것은 AbstractSequentialListsequential access를 하는 자료구조이기 때문에 발생하는 문제이다. 하지만 List 혹은 AbstractList의 관점으로 보면 random access를 하는 자료구조 역시 포함된다. 그리고 그런 케이스에서는 NoSuchElementException는 의미가 없다.

그렇기 때문에, List라는 추상화 수준에 맞게 NoSuchElementException이 아닌 IndexOutOfBoundsException를 던지는 것이다.

아이템 74 메서드가 던지는 모든 예외를 문서화하라

메서드에서 발생하는 모든 예외들에 대해서 문서화를 하는 것을 권장한다. 이를 지키기위한 몇가지 팁을 아이템 74에서 제공한다.

첫 번째로, '검사 예외는 항상 따로 선언하고, 각 예외가 발생하는 상황을 @throws태그를 사용하여 정확히 문서화하자.' 이다.

검사 예외를 따로 선언하지 않고, 상위 예외 클래스 (ex)Exception)로 던지게 되면, 메서드를 살폈을 때, 사용자가 쉽게 이해할 수 없기 때문이다.

아래와 같은 메서드가 존재한다고 하자. 이 메서드는 오직 자연수만을 인자로 받을 수 있는 메서드이다. 그래서 인자인 natrueNumber에 0이 들어온다면 ZeroNumberException를, 음수가 들어온다면, NegativeNumberException를 던진다. 이 메서드를 사용자 입장에서 보면 다음과 같이 보인다.

	public static int addNatureNumber(int natureNumber) throws NegativeNumberException, ZeroNumberException {
		if (natureNumber < 0) {
			throw new NegativeNumberException();
		}
		if (natureNumber == 0) {
			throw new ZeroNumberException();
		}
	    ...
	}

ide를 통해 해당 메서드에 커서를 올려보면 다음과 같이 설명이 나오고, 두 예외가 발생할 수 있다는 점을 쉽게 이해할 수 있다. 하지만 두 예외의 상위 예외를 던진다면 어떨까?

주석을 남겼음에도 불구하고 쉽게 이해가 되지 않는다.

  1. 비검사 예외 역시 @throws태그에 문서화를 한다.

비검사 예외 역시 메서드에서 충분히 발생할 수 있는 예외이고, 프로그래밍적으로 발생할 수 있는 문제가 된다. 예를 들면 잘못된 인자가 전달된 경우가 있을 수 있다. 비검사 예외의 경우 문서화를 통해 어떤 상황에서 발생할 수 있는지를 명시한다면, 개발자는 해당 부분을 고려하여 더 안전한 프로그램을 개발할 수 있다.

아래 사진은 spring data의 CrudRepository의 save 메서드이다. IllegalArgumentExceptionOptimisticLockingFailureException 모두 비검사예외지만 어떤 경우에서 발생하는지에 대해서 작성해줬기 때문에, 프로그래머는 해당 상황을 고려하여 코드를 작성할 수 있을 것이다.

  1. 하나의 클래스에서 동일한 예외가 자주 발생한다면, 클래스에도 추가로 설명을 한다.

관련된 클래스를 찾고 싶었는데 찾지 못했다..

아이템 75 예외의 상세 메세지에 실패 관련 정보를 담아라

예외 메세지를 상세하게 작성하라. 상세한 예외 메세지는 어째서 예외가 발생했는지 이해하기 쉬워 문제가 발생했을 경우, 원인을 파악하는데 도움이 될 것이다.

아래 예시를 보자. 비밀번호가 맞지 않는다고 한다. 아니 제대로 입력했는데, 왜 틀렸다고 할까??

if(!passwordEncoder.match(passward, originPassword)){
	throw new PasswordUnMatchedException(); // 비밀번호가 맞지 않습니다
}

현재 입력한 비밀번호인 passward 변수를 통해 예외 메세지를 자세히 작성해보자.

	public PasswordUnMatchedException(String password) {
		super(String.format("입력하신 비밀번호 %s는 틀렸습니다.")); 
        // 입력하신 비밀번호 는 틀렸습니다.
	}

아! 변수 이름을 password가 아닌 passward라고 해서, 제대로 매핑이 되지 않았던 것 같다.
이런 식으로 찾아낼 수 있을 것이다.

그리고 책에서는 메세지 자체를 파라미터로 받는 것이 아닌 위의 케이스처럼 특정 원인이 되는 변수만을 전달받고, 예외 클래스 내부에서 에러 메세지를 작성하는 것을 권장한다.

아이템 76 가능한 한 실패 원자적으로 만들라

코드를 작성할 때는 실패 원자적으로 만들라고 한다. 원자성이라는 말은 DB의 트랜잭션을 공부하면 접해볼 수 있는 말이다. 어떤 작업을 실행할 때엔, 전부 성공하거나 전부 실패하라는 뜻이다. 이는 자바에서도 마찬가지다. 특정 로직을 실행하는 도중 예외가 발생해서 실패했을 경우, 그 시점까지 변경된 점은 이전 상태로 되돌려야 한다.

그럼 어떻게 실패 원자적으로 만들 수 있을까? 책에서는 4가지 방법을 다룬다.

첫번째로는 불변 객체를 활용하는 방법이다. 불변 객체는 이름 그대로 상태가 변하지 않는다. 그렇기 때문에, 특정 로직이 수행되더라도 상태가 변할 일 없기 때문에, 원자성이 보장된다.

두번째 방법은 작업을 수행하기 이전에, 매개변수의 유효성 검사를 하는 것이다. 유효성 검사를 통해 객체의 상태가 변경되기 이전에 예외를 처리하는 방법이다.

아래 코드는 Vector클래스의 메서드이다. 조금 억지 예시를 들어보자. 위에서 if문을 통해 인덱스 변수에 대한 유효성검사를 하지 않고 코드를 진행한다면 (arraycopy에서 오류가 나겠지만, 안난다고 하자.) 실패한 동작임에도 불구하고 elementCount가 -1이 되고, 해당 위치의 원소는 null이 된다.

    public synchronized void removeElementAt(int index) {
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        modCount++;
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
    }

세번째 방법은 객체의 임시 복사본을 통해 작업을 수행한 후, 성공한다면 원래 객체와 교체하는 것이다.

아래 코드는 List의 sort() 메서드이다. 딱히 도중에 실패할 일은 없겠지만, 정렬을 할 때, 새로운 배열을 만든 후 그 배열을 정렬한다. 그 후에 배열의 값을 자신의 값으로 설정을 해준다. 만약 Arrays.sort(a, (Comparator) c); 과정에서 오류가 발생했다면 임시 객체인 Object[] a가 있기 때문에, 원본에는 변화가 없었을 것이다.

    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

네번째 방법은 작업 도중 예외가 발생한다면, 작업 전의 상태로 되돌리는 복구 코드를 작성하는 방법이다. 책에서는 주로 내구성을 보장하는 자료구조에서 쓰인다고 하고, 자주 쓰이는 방법은 아니라고 한다.

아이템 77 예외를 무시하지 말라

너무 당연한 이야기지만 예외가 있다면 적절한 대응을 해야 한다. 예외가 발생한 try-catch 문에서 catch문을 작성하지 않는 행위는 경고를 무시하는 행위이기 때문이다. 그러니 catch문에는 반드시 로그를 남기던지 이전 상태로 복구를 시키는 등 적절한 동작을 취해야 한다.

하지만 어떻게서든 예외를 무시해야 하는 경우라면, 어떤 이유로 무시했는지에 대해서 작성하라고 한다.

🍃 결론

이번 이펙티브 자바 10장에서는 예외를 사용하는 방법에 대해서 알아보았다. 이제 다시 프로젝트가 시작될텐데 프로그램은 정상적으로 동작하는 경우보다, 이상한 사용자들로 인한 예외 상황이 더 많기 때문에, 이 내용들을 기억하고, 적절한 예외 처리를 해주도록 하자.

@kgh2120 kgh2120 self-assigned this Jan 4, 2024
@kgh2120 kgh2120 changed the title Item 73 ~ 77 예외를 사용하는 방법 Item 73-77. 예외를 사용하는 방법 Jan 4, 2024
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