독서/토비의 스프링

토비의 스프링 Vol.1 4장 예외

Debin 2022. 7. 23.
반응형

4장은 예외 관련 내용입니다.

이전에 강의에서 배운 스프링 DB 1편에서 배운 내용과 비슷한 부분이 있습니다.

 

아래 예외 관련 포스팅도 보시면 좋을 것 같습니다.

https://devdebin.tistory.com/208?category=1028513 

 

예외와 예외 처리

본 게시글은 인프런 김영한 선생님 강의 스프링 DB 1편을 완강하고 배운 것을 남기고자 적은 포스팅입니다. 강의 링크는 아래와 같습니다. https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/das..

devdebin.tistory.com

그럼 포스팅 시작하겠습니다.

사라진 SQLException

 우선 예외를 처리할 때 지켜야할 절대적인 핵심 원칙은 하나다.

모든예외는 적절하게 복구되든지 아니면 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보돼야 한다.

이전 시간에 제일 많이 본 예외 클래스는 바로 SQLException이다.

SQLException은 SQL에 문법이 있거나 DB에서 처리할 수가 없을 정도로 데이터 액세스 로직에 심각한 버그가 있거나,

서버가 죽거나 네트워크가 끊기는 등의 심각한 상황이 벌어졌기 때문이다.

지금까지 실습에서 무책임하게 throws 로 SQLException 예외를 던지거나 예외를 단치 System.out.print()를 통해 로그로 남겼다.

이런 무의미한 예외처리는 절대로 코드에 있어서는 안된다.

자바에서 throw를 통해 발생시킬 수 있는 예외

  1. Error: 시스템에 비정상적인 상황이 발생했을 때 사용. 주로 자바 가상 머신에서 발생시키는 것이므로 애플리케이션 코드로는 잡을 수 없다. 그러므로 애플리케이션에서는 에러를 신경쓰지 않는다.
  2. 체크예외: RuntimException을 상속한 예외들을 제외한 Exception을 상속하는 클래스들이다. throws로 선언해야 한다.
  3. 언체크예외: RuntimeException을 상속한 예외 계열이다. throws로 선언하지 않아도 된다.

예외처리 방법

  1. 예외 복구:
    예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려 놓는 것이다.
    예외처리 코드를 강제하는 체크 예외들은 이렇게 예외를 어떤 식으로든 복구할 가능성이 있는 경우에 사용한다.
  2. 예외처리 회피:
    예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것이다. throws 문으로 선언해서 예외가 발생하면 알아서 던져지게 하거나 catch 문으로 일단 예외를 잡은 후에 로그를 남기고 다시 예외를 던지는 것이다.
  3. 예외 전환:
    예외 회피와 비슷하게 예외를 복구해서 정상적인 상태로는 만들 수 없기 때문에 예외를 메서드 밖으로 던지는 것이다.
    하지만 예외 회피와는 달리, 발생한 예외를 그대로 넘기는 게 아니라 적절한 예외로 전환해서 던진다.

예외 전환은 크게 2가지 목적으로 사용한다.

  1. 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못하는 경우에, 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.
  2. 예외를 처리하기 쉽고 단순하게 만들기 위해 포장하는 것이다. 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.

대부분 서버환경에서는 애플리케이션 코드에서 처리하지 않고 전달된 예외들을 일괄적으로 다룰 수 있는 기능을 제공한다.

어차피 복구하지 못할 예외라면 애플리케이션 코드에서는 런타임 예외로 포장핵서 던져버리고, 예외처리 서비스 등을 이용해 자세한 로그를 남기고, 관리자에게는 메일 등으로 통보해주고, 사용자에게는 친절한 안내 메시지를 보여주는 식으로 처리하는 게 바람직하다.

 

필자는 보통 체크 예외가 터진다면 catch 문에서 언체크 예외로 변환해서 예외를 던진다.
또한 스프링을 사용할 때는 @ExceptionHandler을 사용해는데 이건 예외 처리 회피라고 생각한다.

예외 회피는 예외를 복구하는 것처럼 의도가 분명해야한다고 한다.

콜백/템플릿 처럼 긴밀한 관계에 있는 다른 오브젝트에게 예외처리 책임을 분명히 지게하거나,
자신을 사용하는 쪽에서 예외를 다루는 게 최선의 방법이라는 분명한 확신이 있어야 한다고 하는데,

코드상으로 보면 @ExceptionHandler에서 일반적으로 다루는게 코드도 깔끔하고 최선의 방법이라고 생각한다.

런타임 예외의 보편화

로컬에서 우리가 흔히 개발하는 프로그램은 애플리케이션의 작업이 중단되지 않게 해주고 상황을 복구해야 한다. (자바 Swing 등)

예를 들어 워드의 파일 열기 기능에서 사용자가 입력한 이름에 해당하는 파일을 찾을 수 없다고 애플리케이션이 종료되면 안된다.

 

그러나 자바 엔터프라이즈 서버 환경은 다르다. 수많은 사용자가 독립적인 요청을 보낸다. 이는 독립적인 작업이다.

하나의 요청을 처리하는 중에 예외가 발생하면 해당 작업만 멈추면 된다. 

독립형 애플리케이션과 달리 서버의 특정 계층에서 예외가 발생했을 때 작업을 일시 중지하고 사용자와 바로 커뮤니케이션하면서 예외상황을 복구할 수 있는 방법이 많다.

그러므로 점점 자바의 환경이 서버로 이동하면서 체크 예외의 가치가 떨어졌다고 한다. (throws 코드가 너무 더럽고 기술에 종속적.)

그래서 대응이 불가능한 체크 예외라면 빨리 런타임 예외로 전환해서 던지는 게 낫다.

 

그러나 체크 예외의 의미가 완전히 없어진 것은 아니다. 비즈니스 예외에서는 체크 예외를 종종 사용한다고 한다.

예를 들어 은행에서 출금을 하는데 돈이 부족한 것과 같은 비즈니스적인 예외에서는 체크 예외를 사용한다.

개발자가 잊지 않고 중요한 비즈니스 예외를 처리할 수 있는 일종의 보험 같은 역할인 것이다.

그러므로 사람이면 물론 실수할 수 있지만,
예외에 관한 문서화만 정말 잘 해놓으면 런타임 예외만 사용해도 괜찮지 않을까..? 라는 생각이 들었다.

SQLException

필자가 포스팅에서 남기지는 않았지만 JdbcTemplate에서는 데이터 액세스 로직이 코드에 있지만 throws SQLException을 메서드 선언부에 작성하지 않았다. 왜 없어졌을까? 한 번 생각해보자.

그럼 과연 SQLException이 발생하면 복구가 가능할까? 99%의 SQLException은 코드 레벨에서 복구가 불가능하다고 한다.

생각해보면 쿼리문 문법이 틀리거나, 제약 조건 위반, 데이터베이스 서버 다운, 커넥션 풀에서 커넥션이 부족 등은 코드 레벨에서 복구가 절대 불가능하다. 그저 개발자에게 SQLException 오류가 발생하는 것 외에는 딱히 효과적인 해결책이 없다.

따라서 예외처리 전략을 적용해야 한다. 필요도 없는 기계적인 throws SQLExcption(어차피 예외 복구 불가능.)

선언이 등장하도록 방치하지 말고 가능한 한 빨리 언체크 예외로 전환해줘야 한다.  

 

스프링의 JdbcTemplate 이 예외 처리 방법을 따르므로 메서드 선언부에 throws SQLException을 적지 않았던 것이다.

JdbcTemplate는 모든 SQLException을 런타임 예외인 DataAccessException으로 포장해서 던져준다.

런타임 예외는 throws를 안적어줘도 되므로 SQLException이 모두 사라진 것이다.

DataAccessException도 스프링이 추상화해준 예외다. 스프링을 사용하면서 참 편리한 점이 많다고 다시 느꼈다.

예외 전환

JDBC에는 크게 2가지 한계가 있다.

 

  1. 비표준 SQL
    대부분 DB는 표준 SQL이 아닌 DB에 종속적인 비표준 SQL을 사용한다. 비표준 SQL을 사용해야 DB 사용을 최적화할 수 있다.
    이는 결국 SQL문이 코드에 들어가면 DB에 종속적인 코드가 되는 것이다.
  2. DB 에러정보
    보통 DB마다 에러 코드가 다르다. DB마다 에러 코드가 다르므로 DB를 바꾸면 에러 코드와 관련된 조건문은 전부 수정해야 한다.
    결국 호환성 없는 에러 코드와 표준을 잘 따르지 않는 상태 코드를 가진 SQLException을 가지고는 DB에 독립적인 코드를 작성하기 어렵다.

일단 DataAccessException 예외를 사용하면 2번째 문제를 해결할 수 있다.

DB마다 에러코드가 다른데 DataAccessException은 의미가 같은 예외라면
데이터 액세스 기술의 종류와 상관없이 일관된 예외를 발생하도록 만들어준다.

즉 스프링은 DataAccessException을 통해 데이터베이스에서 발생하는 오류 코드를 스프링이 정의한 예외로 자동으로 변환해준다.

 

이렇게 DataAccessException을 통해 스프링이 정의한 예외로 자동으로 변환해주면 우리는 더 이상 해당 데이터베이스에
종속적인 예외 코드를 신경쓰지 않아 데이터베이스에서보다 독립적이고 유연한 코드를 작성할 수 있다.

예외 추상화가 만능인 것 같지만 추상화로 인해 근본적인 한계가 존재한다.

따라서 학습을 통해 실제로 전환되는 예외의 종류를 확인할 필요가 있다고 한다.

 

확실히 위에 있는 스프링 DB 1편 예외 포스팅을 보시면 이해가 더욱 쉬울 것 같습니다. 더 세세한 부분도 나와 있구..

이상으로 포스팅을 마칩니다. 감사합니다.

 

참고 자료

토비의 스프링 3.1 Vol. 1 (4장 예외)

반응형

댓글