토비의 스프링 - 4장
이번 장에서는 예외에 대해 알아보도록 하겠습니다.
먼저 에러와 예외의 차이를 알아야 합니다.
에러는 자바 실행 시 비정상적인 상황이 발생 했을 때 사용하게 되는 것으로 어플리케이션 단계에서는 대응 할 수 있는 방법이 없습니다. 하지만 시스템 레벨에서 특별한 작업을 하고 있다면 대응 할 수 있지만 그렇지 않을 경우에는 처리 할 수 있는 방법이 없기 때문에 비정상종료를 막을 수 없습니다.
하지만 예외의 경우에는 개발자들이 만든 어플리케이션 코드의 작업 중에 예외 상황이 발생 했을 떄 동작에 대한 처리를 해주는게 가능하고 비정상종료를 막고 실행 상태를 유지해 주는 것을 말합니다.
이러한 에러와 예외는 적절히 비정상종료를 막고 실행 상태를 유지해주거나 작업이 종료 되었다면 운영자 혹은 개발자에게 분명하게 통보 되어야 한다는 중요한 점을 명심해야 합니다.
에러를 처리하는 방법으로 저는 sentry 라는 에러 모니터링 서비스를 사용하고 있습니다.
다음으로 예외를 처리 하는 방법을 알아보려고 합니다.
예외에는 크게 체크 예외와 언체크 예외로 나뉘게 됩니다. 이는 Java 코드 상으로 Exception 클래스의 서브 클래스이면서 RuntimeException 클래스를 상속한 것을 언체크 예외, RuntimeException 을 상속하지 않는 것을 체크 예외라고 합니다.
좀 더 큰 차이로는 RuntimeException 을 상속한 언체크 예외는 명시적으로 예외처리를 하지 않아도 됩니다. 반대로 체크 예외는 반드시 예외를 처리 해줘야만 합니다. 그렇지 않으면 컴파일 에어를 발생시킵니다. 또한 RuntimeException 은 피할 수 있지만 개발자의 부주의에 의해 발생할 수 있는 경우에 발생하도록 만들어져 있습니다. 즉, 예상 할 수 없는 예외 상황이 아니기 때문에 명시적으로 예외 처리를 할 필요가 없게 됩니다.
이런 명시적인 예외 처리를 해줄 때는 크게 3가지 방법이 있습니다.
-
예외 복구
-
예외처리 회피
-
예외 전환
예외 복구는 말 그대로 예외 상황을 파악하고 문제를 해결해 정상 상태로 돌리는 것을 말합니다. 정상 상태로 되돌리는 것은 예시가 필요한데 책에 나오는 대표적인 예시는 네트워크 환경이 안좋아졌을 때 반복 시도를 하는 것을 들 수 있습니다. 원래라면 환경 문제로 연결이 끊겨야 하지만 반복적으로 네트워크 연결을 시도 함으로 복구를 시도하는 것입니다.
예외처리 회피는 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던지는 것을 말합니다, 결국 호출한 쪽에서 이러한 예외 복구 과정을 진행해야 하지만 이렇게 예외처리 회피를 사용하는 이유는 해당 메서드의 역할과 책임이 아닐 경우에 사용하게 됩니다. 이를 사용할 때도 이전의 SOLID 의 단일 책임 원칙을 생각해서 사용해야 합니다.
마지막으로 예외 전환은 예외처리 회피와 같지만 던지는 예외를 호출한쪽에서 확인 할 수 있는 적절한 형태의 예외로 전환해서 던진다는 점이 다릅니다.
최근엔 예전과는 다르게 언체크 예외가 보편화 되고 있는데 그 이유는 catch, throws 의 코드를 꼭 작성해야하는 체크 예외는 어쩔 수 없는 에러 상황을 복구 시킬 방법이 없을 때는 에러를 복구 시키기 보단 운영자나 개발자에게 에러 상황을 빠르게 알리고 이를 언체크 에러로 변환하는게 더 유효하기 때문입니다. 또 언체크 에러라도 catch 로 잡아서 복구 할 수 있기 때문에 꼭 체크 예외로 할 필요가 없기도 하며 무분별한 catch 와 throws 는 코드의 관리에도 문제를 발생 시킬 수 있습니다.
이런 장점이 있는 언체크 예외, 런타임 에러는 사용에 주의를 기울일 필요가 있습니다. 컴파일러가 예외처리를 강제하지 않기 때문에 신경 쓰지 않으면 예외상황을 충분히 고려하지 않을 수 있습니다.
이런 예외 상황들은 시스템 또는 외부의 예외 상황인데 어플리케이션 자체의 로직에 의해 의도적으로 발생 시키고 catch 를 통해 어떤 조치를 취하도록 하는 예외를 어플리케이션 예외라고 합니다.
어플리케이션 예외는 별도의 리턴 값을 통해 예외를 처리하는 방법과 비지니스적인 의미를 담은 예외를 체크 예외로 만들어 던지는 방법으로 구현 됩니다.
별도의 리턴 값을 통한 예외 처리는 개발자들간의 의사소통과 값을 확인 할 때 체크 로직의 중복 코드가 많이 발생 할 수 있는 문제가 있습니다. 반대로 비지니스적인 의미를 담은 체크 예외로 만들면 체크 예외의 장점을 살려 컴파일 단계에서 이를 확인 할 수 있습니다.
마지막으로 스프링에서 사용되는 예외 처리 중 DataAccessException 에 대해서 살펴보려고 합니다. DataAccessException 은 SQLException 을 포장해 런타임 에러로 변경해 SQLException 에 담긴 다루기 힘든 상세한 에러 정보를 일관성 있는 예외로 전환해 추상화 해주려는 용도로 쓰입니다.
스프링에서는 다양한 DB 를 지원하는데 이때 DB는 SQL 만 다른 것이 아니라 예외의 종류와 원인도 제각각이고 DB 에러 코드 또한 제각각인 상황에서 동일한 상황에서 일관된 예외를 전달 받는다면 효과적인 대응이 가능합니다. 이를 스프링에서는 DB별 에러 코드를 분류해 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 테이블을 만들어서 사용합니다.
또한 DataAccessException 은 SQLException 만이 아닌 ORM 기술처럼 JDBC 와는 다른 방식의 DB 엑세스 기술에서도 사용 할 수 있도록 기술에 독립적인 추상화된 예외를 제공하기도 합니다.