예외란?
자바에서 예외란 "우리가 예상한, 혹은 예상치 못한 일이 발생하는 것을 미리 예견하고 안전장치를 하는 것"을 말한다.
예외의 종류는 3가지다.
- checked exception
- error
- runtime exception 또는 unchecked exception
2, 3번째를 제외한 모든 예외는 1번 checked exception이다.
우선 오류와 예외를 구분할 필요가 있다.
오류(error)는 자바 프로그램 밖에서 발생한 예외를 말하고 시스템이 종료되어야 할 수준의 심각한 문제를 의미한다.
개발자가 미리 예측하여 방지할 수 없다.
ex)
- StackOverflowError : 호출의 깊이가 깊어지거나 재귀가 지속되어 stack overflow 발생 시 던져지는 오류
- OutOfMemoryError : JVM이 할당된 메모리의 부족으로 더 이상 객체를 할당할 수 없을 때 던져지는 오류
반면 예외(excpetion)는 프로그램 안에서 발생한 예외로, 개발자가 구현한 로직에서 발생한 실수나 사용자의 영향에 의해 발생한다.
개발자가 예측하여 방지할 수 있기에 상황에 맞는 예외처리(Exception Handle)를 해야 한다.
큰 차이로는 error는 프로세스에 영향을 주고, exception은 스레드에만 영향을 준다.
예외 클래스 상관 관계도, Throwable
Error와 Exception 클래스는 `Throwable`클래스를 상속받는다.(최상위 클래스는 Object)
성격이 달라도 동일한 이름의 메서드를 사용하여 처리할 수 있도록 하기 위함이다.
그 메서드의 예로 `getMessage`(어떤 예외가 발생되었는지에 대한 메시지), `printStackTrace`(예외가 발생하게 된 메서드들의 호출 관계 출력)이 있다.
Checked Exception, Unchecked Exception
Checked Exception
`Exception`을 상속받는 예외 클래스가 Checked Exception이다.
이 예외는 컴파일 시점에 예외를 catch 하는지 확인하고 처리하지 않는다면 컴파일 에러가 발생하기 때문에 예외 처리를 강제한다.
하지만 모든 예외를 핸들링해줘야 하기 때문에 번거로울 수 있다.
로또 번호를 검증하는 예외를 만들었다고 가정했을 때,
`InvalidLottoNumberException`이 `Exception`을 상속받았다면
public class InvalidLottoNumberException extends Exception {
public InvalidLottoNumberException(int number) {
super("로또 번호는 1부터 45까지의 숫자여야 합니다. 입력값 : " + number);
}
}
이 Exception을 호출하는 메서드에서 try catch로 처리해주지 않았을 때 에러가 발생한다.
public LottoNumber(int number) {
try {
validateNumber(number);
} catch (InvalidLottoNumberException e) {
//처리
}
this.number = number;
}
private void validateNumber(int number) throws InvalidLottoNumberException {
if (!(number >= MIN_NUMBER && number <= MAX_NUMBER)) {
throw new InvalidLottoNumberException(number);
}
}
Unchecked Exception
반대로 `RunTime Exception`을 상속받는 Unchecked Exception은 컴파일 시점에 예외 처리를 확인하지 않는다.
그렇기에 throws 예약어를 활용해 예외를 미리 다 처리하지 않아도 된다.
위의 예외를 `RuntimeException`을 상속받는 것으로 변경하게 되면
public class InvalidLottoNumberException extends RuntimeException {
public InvalidLottoNumberException(int number) {
super("로또 번호는 1부터 45까지의 숫자여야 합니다. 입력값 : " + number);
}
}
throws가 없어도 에러가 발생하지 않는다.
private void validateNumber(int number) {
if (!(number >= MIN_NUMBER && number <= MAX_NUMBER)) {
throw new InvalidLottoNumberException(number);
}
}
그래서 뭘 사용해야 하는지 어떻게 구분할까?
일반적으로는 예외를 강제하지 않아도 돼서 편한 Unchecked Exception을 사용하되, 비즈니스 로직에 해당하는 경우고, 일반적으로 네트워크, 파일입출력, DB 연동 등 시스템과 관련된 부분에서는 CheckedException을 사용하도록 하자.
스프링의 Transcational 어노테이션은 기본 정책이 Unchecked Exception과 Errors이라,
Checked Exception일 경우 롤백이 안된다고 한다. 추가로 롤백되게 설정해줘야 한다고 한다.
개발자가 이미 예외를 처리했기 때문에 그 부분에서 커밋할 부분이 있다고 생각해서일까?
참고: [Spring] @Transactional 롤백은 언제 되는 걸까? - 예외가 발생했는데도 DB 반영이 된다고?
참고했던 블로그에는 아래와 같이 설명되어 있다.
예외가 발생할 여지가 있는 메서드를 호출하는 메서드가 예외를 활용해 무엇인가 의미 있는 작업을 할 수 있다면 Checked Exception을 활용할 수 있습니다.
예외 처리에 대한 책임을 확실하게 넘기는 거죠.
하지만 호출하는 메서드가 예외 상황이나 문제를 해결할 수 없다면 Unchecked Exception을 활용할 수 있습니다.
호출된 메서드에서 예외를 터트려 개발자나 사용자가 에러를 처리할 수 있도록 하는 것이지요.
저는 둘 중 어떤 상황인지 확실하게 결정할 수 없다면 우선 Unchecked Exception을 사용하는 편입니다.
Checked Exception을 처리하기 위해서는 throws를 이용해서 피호출 메서드에서 호출하는 메서드로 예외를 던진다고 정리할 수 있습니다.
이 "던짐"은 해당 예외를 처리할 수 있는 메서드까지 던져지게 될 것입니다.
하지만 이런 무분별한 throws의 활용은 코드의 가독성을 떨어트림과 더불어 어떤 메서드의 어떤 부분에서 예외가 발생했는지 알기 어렵게 만드는 주원인입니다.
이럴 경우에는 try문 안에서 발생하는 Checked Exception을 catch문 안에서 Unchecked Exception으로 바꿔주는 방법을 적용해 볼 수 있습니다. 그 결과 예외가
발생한 메서드에서 예외를 처리하거나 개발자 혹은 사용자에게 처리를 위임할 수 있습니다.
커스텀 예외 클래스
커스텀 예외 클래스를 사용하게 되면 클래스 이름 만으로도 직관적으로 어떤 예외인지 알 수 있다는 장점이 있다. 또한 예외 클래스 내에서 예외 메시지나, 정보, 정보에 대한 처리 메서드를 구현할 수 있기 때문에 응집도가 향상된다.
하지만 커스텀 예외 클래스가 많아지면 복잡해지고 그만큼 관리하기 어렵다는 단점이 있다.
기존의 표준 예외로 직관적으로 처리가 가능하다면 표준 예외를 사용하되 부족하다 싶으면 커스텀 예외를 만들어 사용하는 것이 좋아 보인다.
참고
'💻 Dev > Java' 카테고리의 다른 글
BigDecimal이란? (0) | 2024.10.30 |
---|---|
Immutable(불변성), StringBuffer와 StringBuilder (0) | 2024.10.29 |
자바 코드의 메모리 영역(스택&힙) (0) | 2024.10.29 |
JVM 구조와 동작 과정 (0) | 2024.10.29 |
interface와 abstract(추상) 클래스 (0) | 2024.10.25 |
동등성과 동일성&String.equals() (0) | 2023.11.05 |