토비의스프링vol.1

[토비의 스프링vol.1 4장] 예외처리

ohyujeong 2023. 4. 3. 09:14

예외의 종류와 특징

  • 체크예외 : 명시적인 처리가 필요한 예외, throw를 통해 발생시킬 수 있는 예외 = 일반적인 예외
    • Error
      • java.lang.Error 클래스의 서브클래스
      • 시스템에 비정상적인 상황이 발생했을 경우(VM에서 발생시키는 것, 애플리케이션 코드 X)
      • 애플리케이션에서는 이런 에러에 대한 처리는 신경 쓰지 않아도 됨
    • Exception
      • 애플리케이션 코드 작업 중에 예외상황이 발생했을 경우
      • 체크 예외
        • RuntimeException 클래스 상속 X
      • 언체크 예외 = 런타임 예외
        • RuntimeException 클래스 상속 O - 특별하게 다룸
        • 명시적인 예외처리 X (catch로 잡거나 throws 선언 안 해도 됨)
        • NullPointerException, IllegalArgumentException
        • 예상하지 못했던 예외상황에서 발생하는 것이 아님

예외처리 방법

1. 예외 복구

  • 문제를 해결해서 정상 상태로 돌려놓음
  • 예외처리 코드를 강제하는 체크 예외들은 예외를 어떤 식으로든 복구 가능한 경우에 사용

2. 예외처리 회피

  • 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던짐
  • throws 문으로 선언해서 예외가 발생하면 알아서 던져지게 하거나 catch문으로 예외를 잡은 후에 로그를 남기고 다시 예외를 던지는 것
  • 콜백/템플릿처럼 긴밀한 관계에 있는 다른 오브젝트에게 예외처리 책임을 분명히 지게 하거나, 자신을 사용하는 쪽에서 예외를 다루는 게 최선의 방법이라는 분명한 확신 있어야 함

3. 예외 전환

  • 정상 상태로 복구 못할 때 예외를 메소드 밖으로 던지는 것
  • 예외 회피와는 달리, 발생한 예외 그대로 넘기지 X 적절한 예외로 전환해서 던짐
  • 로우 레벨의 예외를 좀 더 의미 있고 추상화된 예외로 바꿔서 던져주는 것

사용 목적

  • 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위함 -> 서비스 계층에서 적절한 복구 작업 가능
    • 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외로 만듦
  • 예외를 처리하기 쉽고 단순하게 하기 위해 포장하는 것
    • 예외처리를 강제하는 체크예외를 언체크예외(런타임 예외)로 바꾸는 경우
    • 체크 예외를 계속 thorws를 사용해 넘기는 건 무의미, 애플리케이션 코드에서 복구 불가능하면 런타임 예외로 포장해서 다른 계층의 메소드 작성할 때 불필요한 throw 선언 줄임

예외처리 전략

  • 런타임 예외의 보편화
    • 항상 복구할 수 있는 예외가 아니라면 언체크 예외로 만듦
  • 애플리케이션 예외
    • 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고, 반드시 catch 해서 조치를 취하도록 요구하는 예외

Ex) 계좌 출금 메소드 (정상적인 출금처리와 잔고 부족 시 예외상황)

  1. 다른 종류의 리턴 값 돌려줌
    • 0, -1과 같은 특별한 값 리턴
    • 사전에 상수로 정의해둔 표준 코드 사용하지 않으면 개발자 간 소통 문제로 이어짐, if 블록 남발
  2. 정상적인 흐름을 따르는 코드는 그대로 두고, 예외 상황에서는 비즈니스적인 의미를 띤 예외를 던지도록 함
    • 잔고 부족일 경우 InsufficientBalanceException 과 같은 예외
    • 의도적으로 체크 예외로 만듦 -> 개발자가 잊지 않고 예외상황에 대한 로직 구현

예외 전환

  • JPA, JDO, Hibernate 등의 기술은 SQLException과 같은 체크 예외 대신 런타임 예외 사용
  • 위와 같이 오브젝트/엔티티 단위로 정보를 업데이트 하는 경우 낙관적인 락킹 발생할 수 있음
    • 같은 정보를 두 명 이상의 사용자가 동시에 조회하고 순차적으로 업데이트 할 때, 뒤늦게 업데이트한 것이 먼저 업데이트한 것을 덮어쓰지 않도록 막아주는 데 쓸 수 있는 편리한 기능
    • 하지만 ORM마다 다른 종류의 낙관적인 락킹 예외 발생
    • 스프링의 예외 전환 방법 적용하면, DataAccessException 서브클래스인 ObjectOptimisticLockingFailureException으로 통일시킬 수 있음
  • DataAccessException 활용 시 주의 사항
    • SQLException에 담긴 DB의 에러 코드를 바로 해석하는 JDBC의 경우와 달리 JPA나 하이버네이트, JDO 등에서는 각 기술이 재정의한 예외를 가져와 스프링이 최종적으로 DataAccessException으로 변환하는데, DB의 에러 코드와 달리 이런 예외들은 세분화되어 있지 않음

정리

  • 예외를 잡아서 아무런 조취를 취하지 않거나 의미 없는 throws 선언을 남발하는 것은 위험하다.
  • 예외는 복구하거나 예외처리 오브젝트로 의도적으로 전달하거나 적절한 예외로 전환해야 한다.
  • 좀 더 의미있는 예외로 변경하거나, 불필요한 catch/throws를 피하기 위해 런타임 예외로 포장하는 두 가지 방법의 예외 전환이 있다.
  • 복구할 수 없는 예외는 간으한 빨리 런타임 예외로 전환하느 는것이 바람직하다.
  • 애플리케이션의 로직을 담기 위한 예외는 체크 예외로 만든다.
  • JDBC의 SQLException은 대부분 복구할 수 없는 예외이므로 런타임 예외로 포장해야 한다.
  • SQLException의 에러 코드는 DB에 종속되기 떄문에 DB에 독립적인 예외로 전환될 필요가 있다.
  • 스프링은 DataAccessException을 통해 DB에 독립적으로 적용 가능한 추상화된 런티임 예외 계층을 제공한다.
  • DAO를 데이터 엑세스 기술에서 독립시키려면 인터페이스 도입과 런티임 예외 전환, 기술에 독립적인 추상화된 예외로 전환이 필요하다.