자바 예외 이해

존스노우·2023년 11월 16일
0

예외 계층

  • 예외 계층 그림

  • Throwable : 최상위 예외이다. 하위에 Exception 과 Error 가 있다.

  • Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구 불가능한 시스템 예외이다. 애 플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.

  • 상위 예외를 catch 로 잡으면 그 하위 예외까지 함께 잡는다. 따라서 애플리케이션 로직에서는 Throwable 예외도 잡으면 안되는데, 앞서 이야기한 Error 예외도 함께 잡을 수 있기 때문이다. 애 플리케이션 로직은 이런 이유로 Exception 부터 필요한 예외로 생각하고 잡으면 된다.
    참고로 Error 도 언체크 예외이다.

  • Exception : 체크 예외
    애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다.
    Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 RuntimeException 은 예외로 한다.

  • RuntimeException : 언체크 예외, 런타임 예외 컴파일러가 체크 하지 않는 언체크 예외이다.
    RuntimeException 과 그 자식 예외는 모두 언체크 예외이다.
    RuntimeException 의 이름을 따라서 RuntimeException 과 그 하위 언체크 예외를 런타임 예외라 고 많이 부른다. 여기서도 앞으로는 런타임 예외로 종종 부르겠다.

예외 기본 규칙

  • 예외는 잡아서 처리하거나 던져야 한다.
    예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리된다.
    예를 들어서 Exception 을 catch 로 잡으면 그 하위 예외들도 모두 잡을 수 있다. 예를 들어서 Exception 을 throws 로 던지면 그 하위 예외들도 모두 던질 수 있다.

  • 자바 main() 쓰레드의 경우 예외 로그를 출력하면서 시스템이 종료된다.
    웹 애플리케이션의 경우 여러 사용자의 요청을 처리하기 때문에 하나의 예외 때문에 시스템이 종료되면 안 된다. WAS가 해당 예외를 받아서 처리하는데, 주로 사용자에게 개발자가 지정한, 오류 페이지를 보여준다.

체크 예외 기본 이해

  • 체크 예외는 잡아서 처리하거나, 또는 밖으로 던지도록 선언해야한다. 그렇지 않으면 컴파일 오류가 발생한 다.

  • 예외 테스트
  • 예외를 잡거나 던지는 테스트
  • 던지려면 무조건 함수명 옆에 선언 해줘야된다. call() throws MycheckedException
  • 1번 테스트는 캐치로 예외를 잡아주고 함수가 void로 끝난다 정상 처리. 캐치 밑으로 void 리턴

  • 테스트 던지면 바로 실패

  • 예외가 일어난다

  • 잡아서 정상적으로 처리하던가 예외를 터트리던가

  • MyCheckedException 는 Exception 을 상속받았다. Exception 을 상속받으면 체크 예외가 된다. 참고로 RuntimeException 을 상속받으면 언체크 예외가 된다. 이런 규칙은 자바 언어에서 문법으로 정한 것이다.

  • 예외가 제공하는 여러가지 기본 기능이 있는데, 그 중에 오류 메시지를 보관하는 기능도 있다. 예제에서 보는 것 처럼 생성자를 통해서 해당 기능을 그대로 사용하면 편리하다.

  • 전자 테스트에선 service.callCatch() 에서 예외를 처리했기 때문에 테스트 메서드까지 예외가 올라오지 않는다.

  • Exception으로 잡아도 된다

  • catch 에 예외를 지정하면 해당 예외와 그 하위 타입 예외를 모두 잡아준다.

  • 물론 정확하게 MyCheckedException 만 잡고 싶다면 catch 에 MyCheckedException 을 적어주어야 한다

  • 예외를 던져버리는 경우

  • 체크 예외를 처리할 수 없을 때는 method() throws 예외 을 사용해서 밖으로 던질 예외를 필수로 지정해
    주어야 한다. 여기서는 MyCheckedException 을 밖으로 던지도록 지정해주었다.

  • 이런식으로 예외를 던져도 됨.

체크 예외의 장단점

  • 장점: 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아주는 훌륭한 안전 장치이다.

  • 단점: 하지만 실제로는 개발자가 모든 체크 예외를 반드시 잡거나 던지도록 처리해야 하기 때문에, 너무 번 거로운 일이 된다. 크게 신경쓰고 싶지 않은 예외까지 모두 챙겨야 한다. 추가로 의존관계에 따른 단점도 있 는데 이 부분은 뒤에서 설명하겠다.

  • 서비스 입장에서 예외를 처리못하는데 모르겠다!

  • 컨트롤 어드바이스 너가 처리해줬음좋겠다. 그런대 다 throw다 적어줘야되 내가 해결못하는데..

  • 컨트롤 어드바이스까지 넘어가 체크예외를 전부만든다? 과해짐 어케 다만들어

언체크 예외 기본 이해

  • 언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는다는 뜻이다.
  • 언체크 예외는 체크 예외와 기본적으로 동일하다. 차이가 있다면 예외를 던지는 throws 를 선언하지 않고, 생략할 수 있다. 이 경우 자동으로 예외를 던진다.

체크 예외 VS 언체크 예외

  • 체크 예외: 예외를 잡아서 처리하지 않으면 항상 throws 에 던지는 예외를 선언해야 한다.
  • 언체크 예외: 예외를 잡아서 처리하지 않아도 throws 를 생략할 수 있다.

  • 전자 테스트 와 달라진점은 따로 선언을 하지 않아도 된다 메서드 옆에

  • 언체크 예외는 주로 생략하지만, 중요한 예외의 경우 이렇게 선언해두면 해당 코드를 호출하는 개발자가 이 런 예외가 발생한다는 점을 IDE를 통해 좀 더 편리하게 인지할 수 있다.(컴파일 시점에 막을 수 있는 것은 아 니고, IDE를 통해서 인지할 수 있는 정도이다.)

언체크 예외의 장단점

  • 언체크예외는예외를잡아서처리할수없을때,예외를밖으로던지는 throws 예외를생략할수있다.
  • 이 것 때문에 장점과 단점이 동시에 존재한다.
  • 장점: 신경쓰고 싶지 않은 언체크 예외를 무시할 수 있다. 체크 예외의 경우 처리할 수 없는 예외를 밖으로 던 지려면 항상 throws 예외 를 선언해야 하지만, 언체크 예외는 이 부분을 생략할 수 있다. 이후에 설명하겠지 만, 신경쓰고 싶지 않은 예외의 의존관계를 참조하지 않아도 되는 장점이 있다.
  • 단점: 언체크 예외는 개발자가 실수로 예외를 누락할 수 있다. 반면에 체크 예외는 컴파일러를 통해 예외 누락을 잡아준다.

체크 예외 활용

  • 언제 언체크(런타임) 예외를 사용하면 좋을까?

  • 기본적으로 언체크(런타임) 예외를 사용하자.

  • 체크 예외는 비즈니스 로직상 의도적으로 던지는 예외에만 사용하자.
    이 경우 해당 예외를 잡아서 반드시 처리해야 하는 문제일 때만 체크 예외를 사용해야 한다. 예를 들어 서 다음과 같은 경우가 있다.
    체크 예외 예)
    계좌 이체 실패 예외
    결제시 포인트 부족 예외 로그인 ID, PW 불일치 예외

  • 로직상 너무 중요해서 의도적으로 던지는경우에만 체크 예외 사용?

  • 뭔가 반드시 놓치면 안돼 이럴 때 체크 예외 . 경험이 더쌓여야하나

체크 예외 문제점

  • 체크 예외는 컴파일러가 예외 누락을 체크해주기 때문에 개발자가 실수로 예외를 놓치는 것을 막아준다. 그 래서 항상 명시적으로 예외를 잡아서 처리하거나, 처리할 수 없을 때는 예외를 던지도록 method() throws 예외 로 선언해야 한다.
  • 지금까지 이야기를 들어보면 체크 예외가 런타임 예외보다 더 안전하고 좋아보이는데, 왜 체크 예외를 기본 으로 사용하는 것이 문제가 될까?

  • 서비스는 리포지토리와 NetworkClient 를 둘다 호출한다.
    따라서 두 곳에서 올라오는 체크 예외인 SQLException 과 ConnectException 을 처리해야 한다.
  • 그런데 서비스는 이 둘을 처리할 방법을 모른다. ConnectException 처럼 연결이 실패하거나, SQLException 처럼 데이터베이스에서 발생하는 문제처럼 심각한 문제들은 대부분 애플리케이션 로직에서 처리할 방법이 없다.
  • 서비스는 SQLException 과 ConnectException 를 처리할 수 없으므로 둘다 밖으로 던진다.

  • 시스템 레벨 및에서 내려오는 에러들은 처리 방법이 없다

  • 컨트롤러도 두 예외를 처리할 방법이 없다.

  • 다음을 선언해서 예외를 밖으로 던진다.

  • 웹 애플리케이션이라면 서블릿의 오류 페이지나, 또는 스프링 MVC가 제공하는 ControllerAdvice 에서 이런 예외를 공통으로 처리한다.

  • 이런 문제들은 보통 사용자에게 어떤 문제가 발생했는지 자세히 설명하기가 어렵다. 그래서 사용자에 게는 "서비스에 문제가 있습니다." 라는 일반적인 메시지를 보여준다.

  • 보통 500 에러

  • 이렇게 해결이 불가능한 공통 예외는 별도의 오류 로그를 남기고, 개발자가 오류를 빨리 인지할 수 있 도록 메일, 알림(문자, 슬랙)등을 통해서 전달 받아야 한다.

  • 복구 불가능한 예외
  • 의존 관계에 대한 문제가 2가지 문제를 코드를 통해 알수 있음

의존 관계에 대한 문제

  • 체크 예외의 또 다른 심각한 문제는 예외에 대한 의존 관계 문제이다.
    앞서 대부분의 예외는 복구 불가능한 예외라고 했다. 그런데 체크 예외이기 때문에 컨트롤러나 서비스 입장 에서는 본인이 처리할 수 없어도 어쩔 수 없이 throws를 통해 던지는 예외를 선언해야 한다.

  • 서비스가 디비에 의존을 하게 된다. 이러면 다른 기술로 변경시 제약이 생김

정리

  • 처리할 수 있는 체크 예외라면 서비스나 컨트롤러에서 처리하겠지만, 지금처럼 데이터베이스나 네트워크 통 신처럼 시스템 레벨에서 올라온 예외들은 대부분 복구가 불가능하다. 그리고 실무에서 발생하는 대부분의 예외들은 이런 시스템 예외들이다.
  • 문제는 이런 경우에 체크 예외를 사용하면 아래에서 올라온 복구 불가능한 예외를 서비스, 컨트롤러 같은 각 각의 클래스가 모두 알고 있어야 한다. 그래서 불필요한 의존관계 문제가 발생하게 된다.

언체크 예외 활용

  • 리포지토리에서 체크 예외인 SQLException 이 발생하면 런타임 예외인 RuntimeSQLException 으로 전 환해서 예외를 던진다.

  • 참고로 이때 기존 예외를 포함해주어야 예외 출력시 스택 트레이스에서 기존 예외도 함께 확인할 수 있
    다.

  • 시스템에서 발생한 예외는 대부분 복구 불가능 예외이다. 런타임 예외를 사용하면 서비스나 컨트롤러가 이 런 복구 불가능한 예외를 신경쓰지 않아도 된다. 물론 이렇게 복구 불가능한 예외는 일관성 있게 공통으로 처 리해야 한다.

  /**
   * Issue a single SQL execute, typically a DDL statement.
   * @param sql static SQL to execute
   * @throws DataAccessException if there is any problem
   */
  void execute(String sql) throws DataAccessException;
  • 예) method() throws DataAccessException 와 같이 문서화 + 코드에도 명시
  • 런타임 예외도 throws 에 선언할 수 있다. 물론 생략해도 된다.
  • 던지는 예외가 명확하고 중요하다면, 코드에 어떤 예외를 던지는지 명시되어 있기 때문에 개발자가 IDE를 통해서 예외를 확인하가 편리하다.
  • 물론 컨트롤러나 서비스에서 DataAccessException 을 사용하지 않는다면 런타임 예외이기 때문에 무시해도 된다.

예외 포함과 스택 트레이스

  • 예외를 전환할때 기존 예외를 꼭!!! 꼭!! 포함 시켜야 된다.

  • // e.printStackTrace(); 하는건 지양하자


  • 기존 예외가 들어갔다 잡았다.!
  • 기존 익셉션을 가지고 있으니?
  • 예외를 포함해서 기존에 발생한 java.sql.SQLException 과 스택 트레이스를 확인할 수 있다.
  • 실무에서 많이 실수하는 경우?

  • 빠뜨리는 경우

  • 뭐때문에 얘가 발생했지?

  • 예외를 포함하지 않아서 기존에 발생한 java.sql.SQLException 과 스택 트레이스를 확인할 수 없다. 변 환한 RuntimeSQLException 부터 예외를 확인할 수 있다. 만약 실제 DB에 연동했다면 DB에서 발생한 예 외를 확인할 수 없는 심각한 문제가 발생한다.

profile
어제의 나보다 한걸음 더

0개의 댓글