예외처리

프로그램이 에러가 발생했을 때 그에 맞는 적절한 처리를 하고 싶을 때 try, catch, finally 등을 이용해서 에러를 처리하는 방법

  • Runtime Exception (unchecked exception)

    실행 시 발생하는 예외. 발생 할수도 발생 안 할수도 있는 예외. 컴파일러에서 예외처리를 강제하지 않는다. (NullPointerException, IllegalArgumentException)

  • Exception (checked exception)

    컴파일 시 발생하는 예외. 프로그램 작성 시 예측가능한 예외. 컴파일러에서 예외처리를 강제한다. 예외를 처리하는 코드를 함께 작성해야 한다.

  • throw vs throws

    throw : 예외를 발생시킴

    throws : 예외 처리의 책임을 외부로 전가시킴

  • Error

    java.lang.Error 클래스의 서브크래스들이다. 에러는 시스템에서 비정상적인 상황이 발생한 경우. 시스템 레벨에서 특별한 작업을 하는 게 아니라면 어플리케이션에서 처리하면 안된다. (OutOfMemoryError, ThreadDeath)

예외처리 방법

  • 예외 복구

예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려 놓는 것

  • 예외처리 회피

예외처리를 자신이 담당하지 않고 자신을 호출한 쪽에 던져버리는 것. throws 문으로 선언해서 예외가 발생하면 알아서 던져지게 하거나 catch 문으로 일단 예외를 잡은 후에 로그를 남기고 다시 예외를 던지는 것이다.

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

  • 예외 전환

예외 회피와 비슷하게 예외를 복구해서 정상적인 상태로 만들 수 없기 때문에 예외를 메소드 밖으로 던지는 것이다. 하지만 예외 회피와 달리, 발생한 예외를 그대로 넘기는 게 아니라 적절한 예외로 전환해서 던진다는 특징이 있다. 예외 전환은 보통 두 가지 목적으로 사용된다.

  1. 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여하지 못하는 경우에, 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다. API가 발생하는 기술적인 로우레벨을 상황에 적합한 의미를 가진 예외로 변경하는 것이다.

    의미가 분명한 예외가 던져지면 서비스 계층 오브젝트에는 적절한 복구 작업을 시도할 수가 있다.

    보통 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외(nested exception)로 만드는 것이 좋다. 중첩 예외는 getCause() 메소드를 이용해 처음 발생한 예외를 확인할 수 있다.

     // 생성자를 이용하는 중첩 예외 만들기
     catch(SQLException e){
         throw DuplicateUserIdException(e);
     }
     // initCause() 메소드를 이용한 중첩 예외 만들기
     catch(SQLException e) {
         throw DuplicateUserIdException.initCause(e);
     }
    
  2. 예외를 처리하기 쉽고 단순하게 만들기 위해 포장(wrap)하는 것이다. 중첩 예외를 이용해 새로운 예외를 만들고 원인이 되는 예외를 내부에 담아서 던지는 방식은 같다. 하지만 의미를 명확하게 하려고 다른 예외로 전환하는 것이 아니다. 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.

    ex1) EJB의 EJBExcepetion : EJB가 EJBException 런타임 예외를 받으면 이를 시스템 예외로 인식하여 트랜잭션을 자동으로 롤백해준다. 런타임 예외이기에 다른 EJB 컴포넌트를 사용하는 EJB나 클라이언트에서 일일이 예외를 잡거나 다시 던지는 수고를 할 필요가 없다.

    반대로, 애플리케이션 로직상에서 예외조건이 발견되거나 예외상황이 발생할 수 있다. 이는 API가 던지는 예외가 아니라 애플리케이션 코드에서 의도적으로 던지는 예외다. 이때는 체크 예외를 사용하는 것이 적절하다. 비즈니스적인 의미가 있는 예외는 이에 대한 적절한 대응이나 복구 작업이 필요하기 때문이다.

예외처리 전략

런타임 예외의 보편화

체크 예외가 일반적인 예외를 다루고, 언체크 예외는 시스템 장애나 프로그램상의 오류에 사용한다고 했다. 체크 예외가 복구할 가능성이 조금이라도 있기에 catch와 throws 선언을 강제하고 있다. 자바 엔터프라이즈 서버환경은 독립형 애플리케이션과 달리 수많은 사용자가 동시에 요청을 보내고 각 요청이 독립적인 작업으로 취급된다. 하나의 요청을 처리하는 중에 예외가 발생하면 해당 작업만 중단시키면 그만이다. 서버의 특정 계층에서 예외가 발생하면 작업을 일시 중지하고 사용자와 바로 커뮤니케이션하면서 예외상황을 복구할 방법이 없다.

그렇기에 애플리케이션 차원에서 예외상황을 미리 파악하여, 예외 발생을 차단하는 게 좋다. 또는 프로그램의 오류나 외부 환경으로 인해 예외가 발생하면 빨리 해당 요청의 작업을 취소하고 서버 관리자나 개발자에게 통보해주는 편이 좋다. 대응 불가능한 체크 예외라면 런타임 예외로 전환해서 던지는 게 낫다.

런타임 예외를 사용하는 경우 API 문서나 레퍼런스 문서 등을 통해, 메소드를 사용할 때 발생할 수 있는 예외의 종류와 원인, 활용 방법을 자세히 설명해두자.

애플리케이션 예외

  • 시스템 또는 외부의 예외상황이 원인이 아니라 애플리케이션 자체의 로직에의해 의도적으로 발생시키고, 반드시 catch 해서 무엇인가 조치를 취하도록 요구하는 예외를 애플리케이션 예외라고 한다.
  • 정상적인 흐름을 따르는 코드는 그대로 두고, 예외 상황에서는 비즈니스적인 의미를 띤 예외를 던지도록 만든다. 정상적인 흐름을 따르지만 예외가 발생할 수 있는 코드를 try블록 안에 깔끔하게 정리해두고 예외상황에 대한 처리는 catch 블록에 모아 둘 수 있기 때문에 코드를 이해하기 좋다. 번거로운 if문을 남발하지 않을 수 있다.
  • 이때 사용하는 예외는 의도적으로 체크 예외로 만든다. 그래서 개발자가 잊지 않고 자주 발생 가능한 예외상황에 대한 로직을 구현하도록 강제해주는 게 좋다.

DAO 인터페이스와 DataAccessException 계층구조

DataAccessException은 JDBC의 SQLException을 전환하는 용도로만 만들어진 건 아니다. JDBC 외의 자바 데이터 액세스 기술에서 발생하는 예외에도 적용된다. JDO, JPA, TopLink, Hibernate, iBatis는 JDBC와 성격과 사용 방법은 다르지만, DataAccessException은 의미가 같은 예외라면 데이터 액세스 기술의 종류와 상관없이 일관된 예외가 발생하도록 만들어준다. 데이터 액세스 기술에 독립적인 추상화된 예외를 제공하는 것이다.

DAO 인터페이스와 구현의 분리

  • DAO를 따로 만들어 사용하는 이유
    1. 가장 중요한 이유는 데이터 액세스 로직을 담은 코드에서 분리해놓기 위해서
    2. 분리된 DAO는 전략 패턴을 적용해 구현 방법을 변경해서 사용할 수 있게 만들기 위해서 (DAO를 사용하는 쪽에서는 DAO의 내부 동작 과정과의 관계가 없어진다)
  • DAO의 사용 기술과 구현 코드는 전략 패턴과 DI를 통해서 DAO를 사용하는 클라이언트에게 감출 수 있지만, 메소드 선언에 나타나는 예외정보가 문제가 될 수 있다. (데이터 액세스 기술의 API는 자신만의 독자적인 예외를 던진다. 따라서, DAO 인터페이스의 메소드에 액세스 기술에 따른 예외를 달아야 할 지 모른다!)

데이터 액세스 예외 추상화와 DataAccessException 계충구조

스프링은 자바의 다양한 데이터 액세스 기술을 사용할 때 발생하는 예외들을 추상화해서 DataAccessException 계층구조 안에 정리해놓았다.

JdbcTemplate과 같이 스프링의 데이터 액세스 지원 기술을 이용해 DAO를 만들면 사용 기술에 독립적인 일관성 있는 예외를 던질 수 있다. 결국 인터페이스 사용, 런타임 예외 전환과 함께 DataAccessException 예외 추상화를 적용하면 데이터 액세스 기술과 구현 방법에 독립적인 이상적인 DAO를 만들 수 있다.

출처

토비의 스프링(책)

댓글남기기