스프링을 사용하다보면 CheckedException(검사 예외)를 만날 경우가 적다. CheckedException을 내보내는 경우 스프링이 UncheckedException(비검사 예외, RuntimeException)으로 전환하여 던지기 때문이다. 그러면 왜 스프링은 CheckedException을 UncheckedException으로 변환하는가?
에 관한 의문이 떠오른다.
여기서는 4가지 문제에 관해 살펴본다.
일단 CheckedException이 명시된 메서드를 사용하는 클라이언트는 세 가지 대응 방안이 있다.
1, 2번을 바람직한 대응이라고 보자. 이 경우 사용하는 모든 메서드에서 강제로 예외 던짐, 처리를 해줘야한다. 예외 던짐에는 메서드 시그니처에 throws
가 동반되며 예외 처리에는 try - catch
가 동반되기 때문에 코드에 가독성을 해치고 해당 메서드의 핵심 로직을 파악하기 어렵게 만든다. try - catch
를 한 번이라도 아름답다고 생각한 적이 있다면 눈에 코로나가 변이가 발생한게 아닌지 의심 해봐야한다.
public void foo() throws CheckedException {
...
public void foo() {
try {
...
} catch {
...
}
이전에는 애써 3번 예외 무시
를 무시했는데, 정말 무시할 수 있을까? 덕지덕지try - catch
작성하기 귀찮고 예외 처리는 해줘야겠고 일단 빈 catch
문을 작성하고 나중의 자신에게 떠넘기는 경우가 있지 않을까? 에이~ 그런 개발자가 세상에 어딨어요... 프로그램 뿐만이 아닌 모든 세상은 불확실성으로 가득 차있고 이런 경우는 의외로 빈번하다. 어떤 문제가 발생할까?
public void blackhole() {
try {
...
} catch {}
귀찮은 개발자가 무시한 예외 덕분에 예외가 터져도 디버깅도 안되고 코드를 뒤져보기 전까지는 파악할 수도 없는 프로젝트의 거대한(그러나 역설적으로 비어있는) 블랙홀이 생겼다. 컴포즈 커피 5잔을 내리 들이키며 떠오르는 태양과 함께 아침을 맞기 싫다면 이 문제를 무시하지 말자.
Checked Exception을 뱉는 메서드를 사용하는 객체는 반드시 해당 예외를 알아야한다(의존한다).
그 예외가 그 객체가 꼭 알아야하는 내용일까?
Jdbc 기술을 사용하는 Dao에서 SqlExeption을 던진다고 하자. 이를 사용하는 Service나 Controller는 SqlException을 알고 있어야 한다. 심지어 예외를 던졌을 경우 처리까지 해줘야한다. 계층간 의존성 분리를 위해 기껏 나누었건만, 하위 계층의 기술에 종속적인 계층이 되는 것이다.
Dao에서 다른 기술을 사용하여 SqlException을 더는 던지지 않도록 했다고 하자. 그러면 기존 Dao와 의존하던 Service와 Controller를 모두 수정해야한다. 수정해야 할 원인이 하나씩 늘어나게 되는 것이다.
스프링은 개발자가 객체지향적으로 개발할 수 있도록 전폭적으로 지원해주는 프레임워크다. 이를 이루기 위한 하나의 방법으로 Interface를 활용한 개발을 적극적으로 지원한다(DI, IoC 등). 그러나 Checked Exception 이 포함된 메서드를 사용한다면 Interface도 반드시 해당 예외를 의존
해야한다.
구현체는 Interface에서 정의된 예외의 extends 타입만 선언 가능하기 때문이다. 이때문에 확장성이라는 Interface의 장점이 퇴색된다.
현재 Interface가 SqlException에 의존하는 경우를 예시로 들어보자.
자, Dao의 DB 접근 기술을 바꿔 더 이상 SqlException을 뱉지 않는다고 가정하자. 그러면 인터페이스
자체를 수정해야 한다. 인터페이스 변경의 파급은 모든 구현체에 퍼진다.
우리가 왜 JDBC를 사용하는가? 다양한 Datasource를 기존 코드 변경 없이 자유롭게 확장, 교체할 수 있도록 하기 위함이 아니었나?
Checked Exception을 덕지덕지 사용하는 경우에는 이런 이점을 전혀 누릴 수 없다.
Runtime Exception은 사용측에 예외 처리를 강제하지 않기 때문에 상위 계층에서 전혀 의존하지 않아도 된다. 누구든지 예외를 처리해주기만 한다면, 예외가 중간에 어떤 경로를 거치든 상관하지 않는다. 우리는 ExceptionAdvice를 통해 Runtime Exception처리를 일원화 시킬 수 있다.
이로써 다른 클래스가 예외 관련으로 수정되는 상황을 막고, ExceptionAdvice만의 관심사로 분리할 수 있게 한다.
적절하게 추상화된 Runtime Exception을 정의하면 특정 구현에 종속적인 Checked Exception 처리를 피하고 일반화된 예외 처리를 가능하게 한다. 명시적으로 예외를 파악할 수 있다는 장점도 얻을 수 있다.
Unchecked Exception의 최대의 장점은 개발자가 예외 처리를 하지 않을 수 있다는 점, 예외가 감출 수 있다는 점이다. 이 점은 동시에 단점이 되는데, 예외를 추적하기 어렵게 만들 수 있다. 따라서 적절한 문서화를 통해 API를 사용하는 입장에서 어떤 예외가 발생할 수 있는지 문서화되어야한다.