본문 바로가기

스프링

[java] 예외처리_낙관적인예외 try catch throws throw

예외블랙홀 : 예외를 잡고 아무것도 안하겠다. 절대 만들어서는 안되는 코드. 예외를 무시하고 지나간다. 왜 안되는지 운영자는 찾기 힘들어 진다.

 

콘솔로그를 누군가 계속 모니터링 하지 않는 이상 심각한 폭탄으로 남아 있을 것이다.

* 예외처리 핵심 원칙

> 적절하게 복구되든지

> 작업을 중단시키고 운영자, 개발자에게 통보가 되어야 한다.

* 예외 블랙홀 보다는 낫지만 문제점

무엇을 실행중에 예외가 났는지, 습관적으로 붙여넣은 것인지 알 수 없다.

이 메소드를 사용 하는 메서드들은 throws Exception을 따라 붙일 수 밖에 없다.

>  결과적으로 적절한 처리를 할 수 있는 예외상황도 그 기회를 박탈당한다.(method1)에서 다른 예외를 잡아도 method2를 사용함으로 인해 무조건 던지므로 예외가 발생함

 

1.     Java.lang.Error(좌측) : 주로 JVM이 발생시키는 것으로, OutOfMemoryError, ThreadDeath 같은 에러는 잡아봤자 아무런 대응 방법이 없다. 신경쓰지 않아도 된다.

2.     Java.lang.Exception(우측) : 개발자들이 만든 코드 중에 예외가 발생했을 경우에 사용됨.

3.     Checked Exception : 일반적인 예외. 반드시 처리해야할 예외. Catch or throws 하지 않으면 컴파일 단계에서 에러 발생.(IOException, SQLException ) 예외적인 상황에서 던져질 가능성이 있는 것들.

4.     RuntimeException : 예외처리를 컴파일단계에서 강제하지 않는 에러(NullPointException, IllegalArgumentException 허용되지 않는 값을 사용해서 메서드 호출 등). 개발자의 부주의

 

네트워크가 불안정해서 DB서버 접속실패로 SQLException이 발생하는 경우

 

예외복구 예시

콜백과 템플릿처럼 긴밀하게 역할을 분담하고 있는 관계가 아니라면 그냥 예외를 던져버리는 것은 무책임한 책임회피일 수 있다.

생각없이 DAO에서 던져진 SQLException은 서비스, 컨트롤러에서도 계속 다시 던져 서버로 전달되고 말 것이다.

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

예외전환 : 원래 DuplicateUserIdException은 내가 만든 예외로 SQLException(체크예외) 에 속하지만, 계속 throws를 사용해 넘기는 건 무의미하다. 어짜피 복구 불가능한 예외라면 RuntimeException으로 포장해서 던져, 다른 계층의 메소드를 작성할 때 무의미한 throws 선언이 들어가지 않게 해줘야 한다.

 

Throwable : stack에 대한 정보를 관리할 수 있다. Exception이 떳을 때, 어느 코드의 몇 번째 줄에서 발생했고 어디서 호출되었는지 처리가됨으로 Runtime 예외에 인자로 넣어줌.

 

* 예외전환
> 예외 회피와 비슷하게 예외를 복구해서 정상적인 상태로 만들 수 없기에 적절한 예외로 전환해서 던지는 특징.
> 새로운 사용자 등록시 중복되는 사용자가 있을 경우 SQLException이 발생하면 쉽게 알 방법이 없다. DuplicateUserIdException 같은 예외로 바꿔 던져주면 서비스 계층 오브젝트에서 적절한 복구 작업을 시도할 수 있다.
> 따라서 독립적이며 의미가 분명한 예외로 전환(DuplicateUserIdException)해서 던져줄 필요가 있다.
> 중첩예외(nested exception) : 주로 체크예외를 런타임예외로 바꾸는 것
> 중첩예외 처음 발생한 예외가 무엇인지 확인 가능한 방법
> 49~52. initCause(), 53. 생성자에 근본 원인이 되는 예외를 넣어주면됨
> 주 목적 : 의미를 명확히 하려고 다른 예외로 전환하는 것이 아닌, 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 주로 사용됨
> 예외를 잡아도 복구할 만한 방법이 없는 경우 런타임 예외로 변경하여 다른 메소드에서 일일이 예외를 잡거나 다시 던지는 수고를 할 필요가 없어진다.
> 60. 예외포장 : DuplicatedUserIdException은 add 메소드에서 잡아서 대응 할 수 있지만 , 다른 SQLException은 대부분 복구 불가능한 예외이므로 잡아봤자 처리할 것도 없고, 결국 throws를 타고 계속 앞으로 전달되어 어플리케이션 밖으로 던져질 것이다. 그럴바에는 그냥 런타임 예외로 포장해 던져버려서 그 밖의 메소드들이 신경 쓰지 않게 해주는 편이 낫다. Throw를 통해 예외로 선언할 수 있으니 문제될 것은 없다.(예외난 곳을 로그로 알수 있기 때문)

 

* AWT, 스윙을 사용한 독립형 어플리케이션은 예를 들어 파일 열기 기능에서 사용자가 입력한 이름의 파일을 찾을 수 없어 어플리케이션이 종료되게 해버릴 순 없다. 복구해주는 로직이 필요하다.
* 하지만 웹등 엔터프라이즈 서버환경은 다르다. 수많은 사용자가 동시에 요청을 보내고, 수많은 요청 중 예외가 일어나는 해당 작업만 중단시키면 된다. 복구방법이 없다. 예외가 발생하지 않도록 차단하거나, 외부 환경에 의한 편이면 서버관리자나 개발자에게 통보해주는 편이 좋다.
* 자바의 환경이 서버로 이동되면서 체크예외의 활용도와 가치는 점점 떨어지고 있다. 의미없는 throws 메소드를 낳을 뿐이다. 대응이 불가능한 체크 예외라면 런타임 예외로 전환해서 던지는게 낫다.
* 예전에는 복구할 가능성이 조금이라도 있으면 체크예외로 만든다고 생각했는데, 지금은 항상 복구할 수 있는 예외가 아니라면 일단 언체크 예외로 만드는 경향이 있다.
* 언체크 예외도 얼마든지 catch 블록으로 잡아서 복구하거나 처리할 수 있다.

 

 

 

3. RuntimeException : 다른 메소드에서 신경쓰지 않도록 런타임 에러를 상속.

4~6. 중첩예외를 만들 수 있도록 생성자를 추가해준다.

Throwable 예외를 처리할 수 있는 최상위 클래스. 에러가 어디서 발생되었고, 어디서 호출되었는지 처리되는 것 Throwable을 통해 부모 생성자에 보내줘야함.

> 메시지나 필요한 정보를 더 넣을 수 있다. Runtime > Thowable 까지 넘어가는데 API로 생성자를 까보면 알 수 있음.

 

46~51로 인하여 이제 체크예외(SQLException)에서 언체크 예외로 변환되어 다른 곳에서 add메서드 사용시 throws는 포함시킬 필요가 없으며, 아이디 중복 상황을 처리하기 위한 DuplicateUserIdException을 이용해서 중복이라는 메시지를 던져준다던지, 아이디를 직접 제공해주는 로직 등 다양하게 이용할 수 있다.

* 단, 런타임 예외로 만들었기에 예외상황을 충분히 고려하지 않을 수도 있기 때문에 API문서나 레퍼런스 문서 등을 통해 예외의 종류와 언인, 활용 방법을 자세히 알아야한다.

47. MySQL 전용코드 OracleDB가 바뀐다면 기대한대로 동작하지 않는다.

> 결국 호환성 없는(DB마다 코드가 다르므로 에러코드와 상태코드를 가진 SQLException 만으로 DB에 독립적인 유연한 코드를 작성하는 것은 불가능에 가깝다.(UserTest 다음에 설명 참조)

 

여긴 볼필요 없는 DAO. 따라해볼려면 작성

 

32 라인의 add를 사용하는 메소드는 throws 를 생략 가능해짐

만약 기본키를 중복으로 입력하면 해당 익셉션이 첫줄부터 보여진다.

* 낙관적인 예외 : 런타임 예외 중심의 전략. 일단 복구할 수 있는 예외는 없다고 가정하고 예외가 생겨도 어차피 런타임 예외이므로 시스템 레벨에서 알아서 처리해줄 것이고, 꼭 필요한 경우는 런타임 예외라도 잡아서 복구하거나 대응해 줄 수 있으니 문제될 것이 없다는 낙관적인 태도 기반

044.XML설정_DAO 분리편

> 해당 키값을 인자로 받아 SQL을 던져주는 서비스 구현시, SQL을 가져오다 어떤 이유에서든 실패하는 경우에 대비한 Exception. 이러한 경우 복구가 불가능하므로 런타임 예외로 정의.

 

10. Throwable 예외를 처리할 수 있는 최상위 클래스. 에러가 어디서 발생되었고, 어디서 호출되었는지 처리되는 것 Throwable을 통해 부모 생성자에 보내줘야함.

> 메시지나 필요한 정보를 더 넣을 수 있다. Runtime > Thowable 까지 넘어가는데 API로 생성자를 까보면 알 수 있음.

> 중첩예외를 저장할 수 있는 생성자

> cause 에는 예외를 직접 담으면 됨

 

스프링 설정파일에 map으로 SQL을 셋팅하고, 아래처럼 SQL을 리턴하는데, SQL이 없으면 예외를 던지고, throws를 통해 메소드를 사용하는 쪽에서도 예외처리를 해줄 수도 있지만, 런타임 예외이므로 꼭 해야할 의무는 아님.