본문 바로가기
프로그래밍/Spring

[토비의 스프링 4장] 예외

by Loper Lee 2022. 6. 10.

4장 예외

잘못된 예외처리 습관의 위험성

  • 예외가 발생했는데 catch 블록을 써서 잡아내는 것까지는 좋으나 이후 아무 조치도 취하지 않고 넘어간다면 정말 위험한 일이다.
  • 원치 않는 예외가 발생하는 것보다도 훨씬 더 나쁜 일이다.
  • 프로그램 실행 중 어디선가 오류가 있어서 예외가 발생했는데 그것을 무시하고 계속 진행한다는 걸 의미하기 때문이다.
  • 결국 발생한 예외로 인해 어떤 기능이 비정상적으로 동작하거나, 메모리나 리소스가 소진되어 예상치 못한 다른 문제를 일으킬 것이다.
  • 최종적으로 오작동을 하거나 시스템 오류가 나서 운영자가 알아차렸을 때는 이미 조치를 취하기엔 너무 늦었다.

초난감 예외처리

try {
        ...
} catch (SQLException e) {
        // 예외를 잡고는 아무것도 하지않는다.
}
try {
        ...
} catch (SQLException e) {
        System.out.println(e);
}
try {
        ...
} catch (SQLException e) {
        e.printStackTrace();
}

초난감 예외처리 코드의 문제점

  • 1번 코드의 경우 문제가 있음에도 무시하고 진행한다.
  • 2, 3번 코드의 경우 누군가 콘솔로그를 지속적으로 확인하지 않는다면 에러를 잡아내기 어렵다.

⇒ 최종적으로 오작동을 일으킬 확률이 높다 = 시한폭탄과 같은 코드다.

예외를 처리할 때는 반드시 지켜야할 핵심 원칙은 한가지로, 모든 예외는 적절하게 복구되든지 아니면 작업을 중단시키고 운영자/개발자에게 통보되어야 한다.

무의미/무책임한 throws

public void method1() throws Exception {
        method2();
        ...
}

public void method2() throws Exception {
        method3();
        ...
}

public void method3() throws Exception {
        ...
}
  • EJB가 한창 쓰이던 시절에 흔히 볼 수 있던 코드다.
  • 매번 정확하게 예외 이름을 적어서 선언하기도 귀찮으니 아예 throws Exception 이라는, 모든 예외를 무조건 던져버리는 선언을 모든 메소드에 기계적으로 넣는것
  • 예외 블랙홀 보다는 조금 낫지만, 메소드 선언에 의미있는 정보를 얻을 수 없다는 단점이 있다.
  • 결과적으로 적절한 처리를 통해 복구될 수 있는 예외상황도 제대로 다룰 수 있는 기회를 박탈당하는 것이다.

예외 처리의 종류와 특징

Error

첫째는 java.lang.Error 클래스의 서브클래스들이다. 에러는 시스템에 뭔가 비정상적인 상황이 발생했을 경우에 사용된다. 그래서 주로 자바 VM에서 발생시키는 것이고 애플리케이션 코드에서 잡으려고 하면 안된다. 시스템 레벨에서 특별한 작업을 하는 게 아니라면 애플리케이션에서는 이런 에러에 대한 처리는 신경 쓰지 않아도 된다.

Checked, Unchecked

java.lang.RuntimeException 클래스를 상속한 예외들은 명시적인 예외처리를 강제하지 않기 때문에 언체크 예외라고 불린다. 또는 대표 클래스 이름을 따서 런타임 예외라고도 한다.
에러와 마찬가지로 이 런타임 예외는 catch 문으로 잡거나 throws로 선언하지 않아도 된다. 물론 명시적으로 잡거나 throw로 선언해줘도 상관없다.

예외처리 방법

복구

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

회피

예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것

  • throw문을 이용하거나, catch문으로 일단 예외를 잡은 후 로그를 남기고 다시 예외를 던진다.
  • 예외를 회피하는 것은 예외를 복구하는 것처럼 의도가 분명해야 한다.

전환

예외 회피와 비슷하게 예외를 복구해서 정상적인 상태로는 만들 수 없기 때문에 예외를 메소드 밖으로 던지는 것

  • 하지만 예외 회피와 달리, 발생한 예외를 그대로 넘기는 게 아니라 적절한 예외로 전환해서 던진다는 특징
  • 예외 전환이 사용되는 목적
    • 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못하는 경우에, 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서
    • 예외를 처리하기 쉽고 단순하게 만들기 위해 포장하는 것
    • ⇒ 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용

예외처리 전략

런타임 예외의 보편화

  • 일반적으로
    • 체크 예외 = 복구 가능성이 조금이라도 있는 예외적 상황, 예외처리 강제 X
    • 언체크 예외 = 시스템장애/프로그램오류에 사용, 예외처리 강제 O
  • 독립형 어플리케이션과 다르게 서버는 특정 계층에서 예외가 발생했을때 작업을 일시중지하고, 사용자와 커뮤니케이션해 바로 예외를 처리할 수 있는 방법이 없다.
  • 자바의 환경이 서버로 이동하면서 체크 예외의 활용도와 가치는 점점 떨어지고 있다.
  • 자칫하면 throws Exception으로 점철된 아무런 의미도 없는 메소드들을 낳을 뿐이다.
  • 때문에 대응이 불가능한 체크 예외라면 빨리 런타임 예외로 전환해서 던지는게 낫다.
  • 자바 초기부터 있었던 JDK의 API와 달리 최근에 등장하는 표준 스펙 또는 오픈소스 프레임워크에서는 API가 발생시키는 예외를 체크 예외 대신 언체크 예외로 정의하는 것이 일반화되고 있다.
  • 언체크 예외라도 필요하다면 얼마든지 catch 블록으로 잡아서 복구하거나 처리할 수 있다.
  • 하지만 대개는 복구 불가능한 상황이고 보나마나 RuntimeException 등으로 포장해서 던져야 할 테니 아예 API 차원에서 런타임 예외를 던지도록 한 것이다.
  • 런타임 예외를 사용하는 경우 API문서나 레퍼런스 문서 등을 통해, 메소드를 사용할 때 발생할 수 있는 예외의 종류와 원인/활용방법을 자세히 설명해두자

어플리케이션 예외

  • 런타임 예외 중심의 전략은 굳이 이름을 붙이자면 낙관적인 예외처리 기법이라고 할 수 있다.
  • 일단 복구할 수 있는 예외는 없다고 가정하고 예외가 생겨도 어차피 런타임 예외이므로 시스템 레벨에서 알아서 처리해 줄 것이고, 꼭 필요한 경우는 런타임 예외라도 잡아서 복구하거나 대응해줄 수 있으니 문제 될 것이 없다는 낙관적인 태도를 기반으로 하고 있다.
  • 반면 시스템 또는 외부의 예외상황이 원인이 아니라 어플리케이션 자체의 로직에 의해 의도적으로 발생시키고, 반드시 catch해서 무엇인가 조치를 취하도록 요구하는 예외도 있다.
    • 정상적인 처리를 했을 경우와 애플리케이션 자체의 로직에 의해 의도적으로 예외를 발생시키고자 하는 경우에 각각 다른 종류의 리턴 값을 돌려주는 것
      • 예외 상황에 대한 리턴 값을 명확하게 코드화하고 잘 관리하지 않으면 혼동의 가능성
      • 결과 값을 확인하는 조건문이 자주 등장
    • 정상적인 흐름을 따르는 코드는 그대로 두고, 애플리케이션 자체의 로직에 의해 의도적으로 예외를 발생시키고자 하는 경우에는 비즈니스적인 의미를 띤 예외를 던지도록 만드는 것
      • 이때 사용하는 예외는 의도적으로 체크 예외로 만든다.