우선 해당 글은 자바의 Exception에 대해 알고 있어야 이해를 할 수 있다.
만약 CheckedExcetion, UncheckedException에 대한 개념을 모른다면 다른 곳에서 찾아본 후 글을 읽어보도록 하자.
이번 포스팅은 다음과 같은 내용을 다룬다.
1) Custom Exception 사용 이유
2) CheckedException, UncheckedException의 선택 기준
Custom Exception을 왜 사용해야 할까?
이번에 예외처리를 공부하면서 문득 의문이 들었다.
Custom 예외를 사용하지 않더라도 이미 Runtime Exception을 상속받는 우리에게 친숙한 예외가 존재한다. ex) IllegalArgumentExcetpion(), IllegalStateException(), NullPointException()...
CustomException을 사용했을 때의 장점은 다음과 같다.
에러를 보다 명확하게 구분할 수 있다.
이게 무슨 말이까? 예시를 통해 알아보자.
public class InvalidNameException extends RuntimeException {
public InvalidNameException(String message) {
// ~~~~
}
}
private void validateNameLength(String name) {
if(name.length() >= 4) {
throw new InvalidNameException("잘못된 이름입니다.");
}
}
InvalidNameException을 사용해 보다 명확하게 오류를 표기할 수 있다.
하지만 필자는 CustomException을 사용하는게 이해가 안 되었다. 굳이? 라는 생각이 계속 머릿속에 맴돌았다. 이유는 다음과 같다.
1) 에러메시지를 자세하게 적어주면, 굳이 커스텀 예외를 만들지 않아도 될 것 같았음.
private void validateNameLength(String name) {
if(name.length() >= 4) {
throw new IllegalArgumentException("이름은 4글자를 초과할 수 없습니다.");
}
}
어차피 예외 메세지를 통해서 무슨 오류가 왜 발생했는지 구체적으로 알려줄 수 있는데, 굳이 CustomException을 사용할 필요가 있을까?
2) 만약 만든다면 어디까지 만드는데?
public class NotExistAttendanceException extends RuntimeException{
}
public class InvalidUserInputException extends RuntimeException {
}
public class InvalidNameException extends RuntimeException {
}
특정 예외에 대한 CustomException들을 만들었다. 벌써 클래스가 3개다. 하나의 애플리케이션을 만드는데 수많은 예외 상황이 있을 것이고, 이를 전부 Custom 처리 해줘야 하는걸까? 그렇다면 오히려 개발하는 데 있어 더 번거롭지 않을까?
예외상황이 10개 , 20개가 넘어가면 어떤 오류를 선택해야되는지도 고민이다. 적절한 CustomExcetion을 사용하기 위해 예외 클래스를 확인해봐야 하는 번거로움이 생기지 않을까?
CustomException의 사용처
그렇다면 언제 CustomExcetion을 사용하면 좋을까? 내가 내린 결론은 다음과 같다.
1) 추가적인 디버깅이 필요할 때.
if (index >= arr.length) {
throw new IndexOutOfBoundsException("범위를 벗어났습니다.");
}
배열에 대한 오류가 발생해 IndexOutOfBoundsException이 발생했다. 개발자는 해당 에러가 난 부분을 찾고, 어느 부분에서 왜 오류가 났는지 디버깅을 시작할 것이다. 물론 디버깅을 통해 오류를 찾아낼 수 있지만, CustomException을 사용해 오류 메세지에 오류를 보다 상세히 적어준다면, 디버깅 위치를 찾고, 로그를 찍어 오류를 찾는 수고스러움을 없앨 수 있다.
public class IllegalIndexException extends IndexOutOfBoundsException {
private static final String message = "범위를 벗어났습니다.";
public IllegalIndexException(List<?> target, int index) {
super(message + " size: " + target.size() + " index: " + index);
}
}
이렇게 CustomException을 사용한다면, 보다 똑똑하게 오류 원인을 찾아낼 수 있다.
2) 여러 타입의 Exception이 발생할 수 있는 경우.
2번 또한 예시를 통해 이해해보자.
출근 시간을 입력해주세요.
09:00
다음과 같이 특정 시간을 입력 받아야하는 요구사항이 있다고 가정하자.
이를 코드로 표현하자면 다음과 같이 작성할 수 있다.
public LocalTime inputUserInput() {
Scanner scanner = new Scanner(System.in);
String userInput = scanner.nextLine();
if (uesrInput.isBlank() || userInput == null) {
throw new IllegalArgumentException();
}
return LocalTime.parse(userInput, DateTimeFormatter.ofPattern("HH:mm"));
}
여기서 발생할 수 있는 오류는 총 2가지다.
1) 유저가 "" 빈 공백을 입력했을 경우
2) 유저가 잘못된 형식의 날짜를 입력했을 경우
이 두 가지 상황에 대한 예외처리를 해야하는데, CustomException을 사용해서 처리해보자.
public static LocalTime inputUserInput() {
Scanner scanner = new Scanner(System.in);
String userInput = scanner.nextLine();
try {
if (userInput.isEmpty()) {
throw new IllegalArgumentException();
}
return LocalTime.parse(userInput, DateTimeFormatter.ofPattern("HH:mm"));
} catch (IllegalArgumentException e | DateTimeParseException e) {
throw new InvalidNameException();
}
}
해당 코드를 호출하는 로직에서는, 2가지의 예외가 아닌, InvalidNameException만 처리해주면 된다.
try {
Name.inputUserInput();
} catch (InvalidNameException e) {
// ~~~~
}
Custom Exception에서 CheckedException, UncheckedException 선택
이펙티브 자바에서는 다음과 같이 말한다.
복구할 수 있는 상황에는 검사 예외(Checked Exception)를,
프로그래밍 오류에는 런타임 예외(UncheckedException)를 사용하라.
필자는 복구할 수 있는 상황이 이해가 안 되었다.
예를 들어 유저의 잘못된 입력 ex) [젠슨 -> 젱슨으로 입력받았다면, 재입력을 요청해 젠슨으로 다시 입력받기]. 이는 프로그래밍이 강제로 종료되는 것을 막았다. 이는 복구일까? 그렇다면 복구할 수 없는 상황은 어떤 상황이 있을까?
우테코 크루들과 이야기도 해보고, 네오 코치와도 이야기를 해본 결과 필자가 내린 결론은 다음과 같다.
복구할 수 있는 상황은 매우 드물다
책에서 말하는 복구의 개념은 JVM이 아닌, 보다 상위의 무언가를 말하는 것 같았다.
예시를 들어 설명해보자.
connection에 대한 연결이 끊어졌고, 재요청을 통해 연결을 복구할 수 있다.
try {
network.connect();
} catch (NetworkException e) {
int retryCount = 0;
while (retryCount < 5) { // 5번 재시도
try {
Thread.sleep(2000); // 2초 대기
network.connect(); // 재연결 시도
break; // 연결 성공 시 반복문 종료
} catch (NetworkException | InterruptedException retryException) {
retryCount++;
if (retryCount == 5) { // 5번 재시도 후에도 실패
throw new CustomException("10초 동안 연결 복구 실패", retryException);
}
}
}
}
뭐 대충 이런 코드 아닐까...? 결국 네트워크가 다시 연결이 된다면 복구가 되는거니깐.
다만, 이런 상황은 매우 드물다고 생각한다. Java에서 CheckedException으로 사용하는 예외는 대표적으로
IOException, SQLException이 있다. SQLException은 사실상 체크를 해도, 쿼리를 수정하지 않는 이상 똑같은 오류가 반복된다.
요즘 언어들 (C#, Kotlin)은 CheckedException이 없다. 그래서 필자가 느끼기에는... 특수하게 이건 반드시 예외를 잡아서 처리해야돼! 라고 의도한 상황이 아닌 이상 CheckedException을 사용할 필요가 없지 않나? 생각이 들어 필자가 무언가를 개발할 때 CheckedException을 사용하는 일은 별로 없을 것 같다.
추가로 아직 CustomException의 필요성에 대해 크게 못 느끼는 중이다. 포스팅에서 설명한 다양한 장점들이 존재하지만, 무엇보다 기존에 있는 RuntimeException을 잘 사용하면 되지 않나...? 라는 생각이 든다.
그래서 CustomException이 정말 필요하다고 느낄 때 도입을 해볼 생각이고, 그 전까지는 RuntimeException을 상속받는 예외들을 사용하려고 한다.
(자주 사용되는 Exception 정리본)
Item 72. 표준 예외를 사용하라 | Carrey`s 기술블로그
서론 숙련된 프로그래머는 그렇지 못한 프로그래머보다 더 많은 코드를 재사용한다. 예외도 마찬가지로 재사용하는 것이 좋으며, 자바 라이브러리는 대부분 API에서 쓰기에 충분한 수의 예외를
jaehun2841.github.io
다만, 무지성으로 IllegalArgumentException을 사용하는 것이 아닌, 상황에 맞게 예외처리를 하려고 한다.
해당 포스팅에는 개인적인 견해가 많이 들어갔다. CheckedException/UncheckedException 에 정해진 답은 없다고 생각하고, Custom Exception 또한 마찬가지다. 본인에게 편한 방법을 사용해보자.
참고문서
https://tecoble.techcourse.co.kr/post/2020-08-17-custom-exception/
custom exception을 언제 써야 할까?
우아한테크코스의 두 크루인 오렌지와 우가 싸우고 있다. 왜 싸우고 있는지 알아보러 가볼까? 오렌지 : 아니 굳이 사용자 정의 예외 안 써도 됩니다!! 우 : 아닙니다!! 써야 합니다!!! 사용자 정의
tecoble.techcourse.co.kr
명쾌한 Custom Exception in Java
Custom Exception을 언제 사용해야 할지 알 수 있다 : Standard Exception이 마땅치 않은 상황에 대하여 Custom Exception 작성법을 알 수 있다 : 지켜야 할 것들 어떤 점을 주의해서 사용해야 하는지 알 수 있다
ssoco.tistory.com
https://mangkyu.tistory.com/152
[Java] 체크 예외(Check Exception)와 언체크 예외/런타임 예외 (Uncheck Exception, Runtime Exception)의 차이와
1. 체크 예외(Check Exception)와 언체크 예외/런타임 예외 (Uncheck Exception, Runtime Exception)의 차이 [ 예외(Exception)의 종류 ] 에러(Error) 예외(Exception) 체크 예외(Check Exception) 언체크 예외(Uncheck Exception) 에
mangkyu.tistory.com
'java' 카테고리의 다른 글
상속과 합성 (0) | 2025.03.17 |
---|---|
Java 의 불변 객체 (final, 방어적 복사, unmodifiable) (0) | 2025.03.13 |
Stream (0) | 2025.02.25 |
함수형 인터페이스 (0) | 2025.02.25 |
Thread(3) - volatile, synchronize, lock (8) | 2025.01.19 |