반응형

이 의문이 생긴 이유는 클린 코드 과정을 진행하며 아래와 같은 피드백을 받아 생겨서 글을 작성하게 되었다.

만약 추출 한다면?
대략 아래와 같이 할 생각이었다.

public class IllegalArgumentBlankException extend IllegalArgumnetException {
    throw new IllegalArgumentException("빈 문자는 입력할 수 없습니다.");
} 

하지만 왜 해야 하는지? 의문이 들었다.
이런 의문이 든 의식의 흐름대로 가보겠다.

🔍 어떻게 추출하지? 책을 찾아 보자.

이 때는 별 생각없이 추출하려고 했다. 어떤 좋은 방법이 있을까... 이펙티브 자바, 클린 코드 책을 찾아 봄.
커스텀 예외 클래스에 대한 내용은 없음. 사실 내가 못찾은 것일 수도 있다.

🔍 인터넷에서 좋은 코드를 살펴보자.

그러다가 마주친 우테코 블로그!!!
이 글을 보고 피드백에 의문이 생겨버렸다. 그래서 글을 참고하여 나의 생각으로 정리를 해보았다.

🔍 표준 예외를 적극적으로 사용하자

이 내용은 클린 코드 책에도 나와있는 내용이다. 이유는 간단하다. 모든 개발자가 알아보기 쉽기 때문이다.

1. 예외 메시지로도 충분한 의도 전달이 가능한거 아닐까?

정말 아무것도 하지 않는다. 단지 이름을 재정의 하기 위한 용도일 뿐.
피드백은 의도 전달이라는 명분이 있는데 의도 전달은 빈 문자는 입력할 수 없습니다. 라는 메시지로도
충분할 수도 있다.

public class IllegalArgumentBlankException extend IllegalArgumnetException {

    public IllegalArgumentBlankException() {
        throw new IllegalArgumentException("빈 문자는 입력할 수 없습니다.");
    }
}

2. 표준 예외를 사용하면 가독성이 높아진다.

이유는 간단하다. 모든 개발자가 알아보기 쉽기 때문이다.
그런데 메시지만 받는 커스텀 예외 클래스를 만든다면?? 이 장점이 사라질 수도 있다고 생각했다.
결국 익숙한 예외를 사용하므로써 새로운 예외를 맞이할 경우 해야하는 생각의 시간을 줄여야 한다.

3. 예외 클래스를 계속 만들다 보면 커스텀 예외가 점점... 산더미가 될 수도 있다.

마치 DTO가 점점 많아질 때의 현상과 비슷..
결국 소스 관리에도, 메모리 로딩 속도에도 좋지 않을 수 있다.

🔍 그럼 커스텀 예외는 언제 만들어야 할까?

우테코 블로그에 아주 좋은 코드 예시가 있었다. 아주 좋은 IndexOutOfBoundsException 로 예제를 추가하여 만들었다.

흔히 알고 있는 IndexOutOfBoundsException 이다. 예외 메시지 없이 발생하면
Index 1 out of bounds for length 1 와 같은 메시지를 던진다.
하지만 여기에 추가적으로 더 친절한 예외 메시지가 필요할 경우!! 아래와 같은 커스텀 예외는 필요하다.

class IllegalIndexException extends IndexOutOfBoundsException {
    private static final String message = "범위를 벗어났습니다.";

    public IllegalIndexException(List<?> target, int index) {
        super(message + " size: "  + target.size() + " index: " + index);
    }
}

먼저 커스텀 예외를 사용하지 않고 추가 메시지를 전달할 경우를 살펴 보겠다.
java.lang.IndexOutOfBoundsException: 범위를 벗어났습니다.
이런 메시지가 뜨고 끝이다. 그럼 디버깅을 하며, 사이즈는 몇이고, 벗어난 인덱스는 몇인지 찾아야 할까?
할 수는 있다. 하지만 비효율적이고, 생각만 해도 짜증이 밀려온다.

@Test
void indexOutOfBoundsException() {
    List<Integer> list = new ArrayList<>();
    int currentIndex = 0;
    try {
        list.add(1);
        for (int i = 0; i < 2; i++) {
            currentIndex = i;
            list.get(i);
        }
    } catch (IndexOutOfBoundsException e) {
        throw new IndexOutOfBoundsException("범위를 벗어났습니다.");
    }
}

그렇다면 조금 리팩토링을 해보자. size와 index를 보여주자.
이렇게 하면 된다. 그런데 굉장히 복잡하다. 매번 클라이언트가 이렇게 해줘야 한다고??...
이럴 때 커스텀 예외가 필요하다.

throw new IndexOutOfBoundsException("범위를 벗어났습니다. size: " + list.size() 
                                                                    + " index: " + currentIndex);

이제 위에 작성한 커스텀 예외 클래스를 활용 해보자.
주석 처리된 부분만 사라지고 바로 아래 코드가 커스텀 예외 클래스를 활용한 것이다.
굉장히 아름다워졌다. 이러한 이유로 커스텀 예외 클래스를 추출하지 않아도 된다고 의문이 생기게 되었다.

@Test
void indexOutOfBoundsException() {
    List<Integer> list = new ArrayList<>();
    int currentIndex = 0;
    try {
        list.add(1);
        for (int i = 0; i < 2; i++) {
            currentIndex = i;
            list.get(i);
        }
    } catch (IndexOutOfBoundsException e) {
//      throw new IndexOutOfBoundsException("범위를 벗어났습니다. size: " + list.size() + " index: " + currentIndex);
        throw new IllegalIndexException(list, currentIndex);
    }
}

❗️ 의문에 대한 피드백

위의 나의 생각을 리뷰어님게 공유 드렸더니 확실하게 이해하지 못했던 의도를 알 수 있었다.
테스트 용이성(메시지 검증)이라는 피드백이었는데 메시지를 getter로 추출하여 테스트 메시지를 작성하면,
더 깔끔한 코드가 될 것 같다는 의도였다. 실제로 아래와 같이 테스트를 하고 있었다.

@DisplayName("입력 값이 공백 문자열, 빈 문자열이면 IllegalArgumentException")
@Test
void isBlankEmpty() {
    // given
    StringCalculator stringCalculator = new StringCalculator();
    String input = "";

    // when
    assertThatThrownBy(() -> stringCalculator.calculateString(input))
            // then
            .isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("빈 문자는 입력할 수 없습니다.");
}

이 메시지 검증 부분을 커스텀 예외 클래스로 추출하여 클래스 변수로 선언하면 훨씬 코드가 깔끔 해질 것 같다는
의도!!

public class IllegalArgumentBlankException extend IllegalArgumnetException {
    private static final msg = "빈 문자는 입력할 수 없습니다.";

    public IllegalArgumentBlankException() {
        throw new IllegalArgumentException(msg);
    }

    // 요걸 가지고 
    public String getMsg() {
        return msg;
    }
}

결국 최종 피드백은 나의 생각을 존중 해주셨다. 그래서 추출하지 않기로 결정!!

반응형
복사했습니다!