엄범

 

Error와 Exception의 차이

  • 에러 : 애초에 예상이 불가능한 것.
  • 예외 : 발생을 예상할 수 있는 것. 그리고 예상할 수 있기 때문에 그에 대한 대비로 try-catch가 있는 것.
    • checked exception
      • 컴파일 타임에 경고를 해주는 예외. 예외 처리가 안되어 있으면 컴파일이 안된다.
      • 컴파일 타임에 발생하는 Exception이라고 말하기는 좀 그렇다.
      • 정확히는 Exception은 Runtime에 발생하며 컴파일 타임에 경고를 해주는거지.
      • 대표적인게 `` IOException, SQLException``
    • unchecked exception
      • 컴파일 타임에 경고를 안해주는 예외. 예외 처리가 안되어 있어도 컴파일이 된다.
      • 대표적인 것이 `` NPE, IndexOutOfBoundsException``
    • 자바에서는 `` RuntimeException``과 그 하위 예외들이 unchecked exception, 나머지는 checked exception이다.

 

예외를 어떻게 처리할지 생각하기 전에 항상, 예외를 마주쳤다는 것은 무엇을 의미하는가?를 먼저 생각해야 한다.

1. Exception은 조건문이 아니다. Exception을 if처럼 쓰지 마라.

즉, 조건의 의미가 들어있는게 아니다. 말 그대로 예외다.

의미 상 조건에 맞게 처리해야 하는 경우는 if, 예상치 못한 상황이 왔을 때 어떻게 행동할 것인가는 try - except로 처리한다.

 

조건문은 예상된 flow대로 흘러가는 반면 예외는 try 안의 코드를 실행하다 언제 실행 흐름이 except로 넘어갈지 모른다는 점도 생각해 보면 둘을 구분하는데 도움이 된다.

 

예를 들어서 argv의 개수가 적을 때라던가, 이런 예상할 수 있는 상황은 그냥 일단 argv[0]에 접근하고 안되면 except로 받는 것 보다, ``c if``로 length check를 하는게 더 나아보인다.

 

[*] ``py if`` 안에 ``c return 0``대신 ``py raise`` 쓰는 것은 좋다.  이거랑 위에 적은 내용은 별개임.

특히 문자열 등을 리턴해야 하는 경우 예외 상황에서 리턴으로 처리하게 되면 리턴값을 뭐로 해야하나 애매할 때가 있다. raise 쓰면 됨.

 

2. API에서 발생하는 예외는 "이렇게 쓰지 마라."를 의미할 수도 있다.

예를 들면 ``java JdbcTemplate.queryForObject()``는 DB에서 가져온 데이터가 없으면(null) 예외를 발생시킨다.

근데 이 메서드는 애초에, 꼭 이 데이터를 null 없이 받아야 한다라고 설계되어 있는 메서드다.

그래서 이렇게 없는 데이터를 가져올 수 있는 케이스가 예상이 된다? 라면 예외가 발생하지 않는 다른 메서드를 사용하고 `` if``로 처리하는 것이 자연스럽다.

 

Global Exception Handling은 좋은 패턴이지만 모든 예외를 Global Exception Handler에서만 처리하는 것은 좋지 않다.

예를 들어 위 2번과 같은 상황에서, "어떤 입력에 대해서는 `` queryForObject()``에서 `` RuntimeException``이 발생할 것임을 알지만 그 것을 감안하고서라도 이 메서드를 꼭 사용해야 한다."와 같은 케이스를 생각해보자.

이미 Global RuntimeException Handler가 등록 되어 있다고 하더라도, 굳이 이 Exception을 그리로 보내야 할 이유가 있는가? 

오히려 여기서 잡아서 pass하고 null을 리턴하는게 맞을지도 모른다.

이런 경우 해당 메서드 내에서 try-catch로 예외에 대한 적절한 처리를 하는게 맞을 수 있다.

 

즉, Global Exception Handling은 한 가지의 선택지가 될 수 있는 것이지 모든 처리를 반드시 여기서 하라는 얘기는 아니다.

 

Global Exception Handler를 사용하면 좋은 케이스는 다음과 같다.
  • `` RuntimeException``에 대한 DefaultHandler를 지정할 때. (handleDefaultRuntimeException)
    NPE같은 일일히 예상할 수 없는 unchecked exception에 대해 DefaultHandler를 지정하게 되면 일관된 응답을 기대할 수 있다.
    로깅도 용이하고 갑자기 프로그램이 뻗는 상황도 방지할 수 있을 것이다.
  • 여러 곳에서 비슷한 예외가 발생하고 이에 대해 일관된 처리가 필요하거나, 일관된 응답이 필요할 때.
    • 최상위 unchecked인 RuntimeException에 대한 핸들러가 이미 존재한다고 해도, 그 자식인 다른 unchecked에 대한 핸들러를 지정해서 조금 더 구체적으로 예외 상황을 처리하는 것이 필요할 때가 많다.
    • 물론 checked에 대해서도 가능함.
  • checked exception과 unchecked exception에 대한 처리 로직을 한 곳으로 모으고 싶을 때.
  • try-catch를 하는 부분이 지저분하거나, throws를 해서 메서드 시그니처가 지저분해지는 것을 막을 때

 

checked exception도 Global Exception Handler에서 처리할 수 있다.

checked exception은 반드시 try-catch나 상위로 throws 둘 중 하나를 해야 하기 때문에, checked exception을 처리하는 척 하면서 unchecked exception으로 래핑해서 다시 던지면 Global Handler로 들어가게 할 수 있다.

 

RuntimeException을 상속 받은 RuntimeIOException 클래스를 하나 만들고,

try-catch로 감싼 다음 RuntimeIOException에 checked exception을 담아서 던지도록 한다. (stack trace에 남는다.)

```java

try {

    // IOException 발생 ( checked exception )

} catch (Exception e) {

    throw new RuntimeIOException(e)       // this is unchecked exception!!!

}

```

```java

/* global exception handler */

@ExceptionHandler(RuntimeIOException.class)

public void handleRuntimeIOException(final RuntimeIOException runtimeIOException)

```

 

* assert는 내부적인 정확성을 보장하는데 사용한다.

``py assert``는 내부적인 정확성을 보장하는 데 사용된다.

반드시 어떤 값을 입력 해야만 한다는 느낌으로 정확한 사용법을 강요할 때 쓰는게 아니다.

마찬가지로 예상치 못한 값이 들어왔으니까 exception을 발생시킨다!! 이런 느낌으로 쓰는게 아니다. (이런 경우는 ``py if - raise``를 사용한다.)

따라서 argument로 알맞은 값이 들어왔는지 검증할 때는 사용하지 않는다.