Throwable
: 최상위 예외
Error
: 복구 불가능한 시스템 예외로, 언체크 예외임, 잡으면 안 됨
Exception
: 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외, Exception
과 그 하위 예외는 컴파일러가 체크하는 체크 예외 (RuntimeException
은 예외)
RuntimeException
: RuntimeException
과 그 하위 예외는 컴파일러가 체크하지 않는 언체크 예외, 런타임 예외라고도 함
체크 예외 : 예외를 잡아서 처리하지 않으면 항상 throws
에 던지는 예외를 선언해야 함 (그렇지 않으면 컴파일 에러 발생)
언체크 예외 : 예외를 잡아서 처리하지 않아도 throws
생략 가능
언체크 예외 장단점 (체크 예외는 반대로 생각하면 됨)
장점 : 신경쓰고 싶지 않은 언체크 예외를 무시할 수 있음
단점 : 컴파일러 체크가 되지 않으므로 실수로 처리해야 할 예외를 누락할 수 있음
기본 원칙
대부분의 예외는 복구 불가능
하고, 이런 예외들은 일관성 있게 공통으로 상위 레벨에서(ex. ControllerAdvice
) 처리해야 함
체크 예외를 사용하면 처리할 수 없는 이러한 예외들을 throws
에 선언함으로써 불필요한 의존 관계
가 생기는 문제가 발생함
특정 기술에 종속된 예외에 의존하면 결국 OCP
를 지키지 못하게 되고, DI
의 장점을 누릴 수 없음
throws Exception
으로 해결 가능하긴 하지만 이렇게 선언하면 모든 체크 예외를 모두 밖으로 던지기 때문에 처리가 필요한 중요 체크 예외가 발생해도 이를 인지할 수 없음, 결국 체크 예외의 장점을 누리지 못하는 안티패턴
이므로 피해야 함
런타임 예외
를 활용하면 이런 문제를 해결할 수 있음
서비스나 컨트롤러는 복구 불가능한 예외를 신경쓰지 않고 밖으로 던지고, 상위 레벨에서 일관성 있게 공통 처리하면 됨
throws
에 선언하지 않아도 되므로 의존 관계도 발생하지 않음 (공통 처리하는 부분은 의존 관계가 생길 수 있음)
대신 런타임 예외는 컴파일러 체크가 안 돼서 놓칠 수 있으므로 문서화가 중요함, 코드에 throws 런타임예외
와 같은 형식으로 중요한 예외를 인지할 수 있게 하는 것도 방법임
예외를 변환할 때는 꼭 기존 예외를 Throwable
의 cause
필드로 포함해야 함
포함하지 않는다면 스택 트레이스
를 출력할 때 정확한 예외 발생 원인을 파악할 수 없게 됨