본문 바로가기

Dev.../플밍 관련 자료

[펌] Exception과 메시지



1. Exception과 메시지
자바에서 예외상황은 핸들링 가능한 에러이다. 메시지는 여러 단위업무들을 개발할 때에 사용자에게 보여줄 다양한 내용이다. 그런데 고민스러운 것은 JVM이 캐치하는 에러가 아닌, 로직적인 처리로 인하여 그것을 에러로 처리하여야 할 때이다. 이 때 Exception과 메시지의 구분이 모호하여 지며 어떤 식으로 양자의 갭을 메워야할지의 고민이 생긴다.
가령 executeUpdate()를 수행한 결과값이 0일때, 그러나 원래 예상된 정수값은 1이라면 이것을 어느 범주에서 처리해야 하는가?
단순히 사용자정의 Exception을 발생시키며 문자열을 받는 생성자에 throw new UserException("해당 자료의 업데이트에 실패하였습니다.") 하는 식으로 메시지를 넘기고 지정된 에러페이지에서 getMessage() 해서 받는다면 문제는 없겠지만 고객의 요구사항은 그리 끝나지 않는다.

2. 요구사항 정의
성공일 때의 메시지는 페이지 하단에 텍스트 박스를 만들고 "1건이 입력되었습니다."라는 식으로 보여준다. 그러나 에러 메시지 처리 시는 별도의 팝업을 띄우고 화면에 에러 메시지, 원인, 조치방안, 프로그램ID, SQL CODE를 보여준다.
현재 구성된 프레임웍에서는 기본적으로 에러 상황은 throw new Exception()하도록 되어 있다. 그리고 모든 작업 수행의 리턴은 DataSet이라는 객체를 이용한다. 즉 soft type checking이다.
고객이 sql code를 요청한 의미는 기존 cs의 프로그래밍에 익숙하기 때문인데, 자바는 executeUpdate()해서 리턴된 정수를 이용해 원하는 건수가 반영되었는지의 여부를 판단하나 기존 CS에서는 proC를 통해 sql code를 리턴받을 수가 있으므로 그 코드가 가령 1304(해당 자료가 없습니다)라면 그 상황에서 에러 메시지 처리를 하였다. 문제는 자바는 API정의한 SQLException에 대하여서만 에러코드를 가져올 수 있다는 것이므로 이 부분에 대한 고객의 이해가 필요하다.

3. 구현을 위한 고민
가. First Trial
구현을 위한 가장 고민은 throw new UserException()으로 처리할 것인가 아니면 각각의 개발 소스의 메서드에서 try, catch문으로 할 것인가의 결정이다.
throw를 하게되면 View와 Model의 사이에서 컨트롤러의 역할을 하는 WorkerController.java라는 공통서블릿 한 곳에서 예외처리를 일괄적으로 관리할 수 있다. 여기에는 커넥션의 롤백같은 것이 포함될 것이다.
각각의 단위업무의 메소드에서 try, catch로 사용한다면 롤백을 catch문에서 하고 에러메시지를 DataSet에 담아서 리턴할 수도 있을 것이다.
초기에는 고민이 throw를 하는 경우, 에러메시지를 담는 DataSet을 함께 보낼 수 없다는 것이었다. jdk 1.3에서는 Exception의 생성자는 무인자생성자와 스트링 객체를 가지는 생성자 둘 뿐이지 않는가?! 그래서 스트링객체를 넘기는 생성자에 에러코드값을 넘기고 JSP페이지에서는 예외상황 시에 Exception 객체를 가져오고 UserException.getError(String errCode)를 호출한다. 이는 ErrorFactory.getInstance().getEerror(String errCode)를 부른다.

나. Second Trial
사용자 정의 Exception을 이용하여 메시지처리까지 핸들링하려는 시도는 실패하였다. 현시점에서 얻은 결론은 JDK1.3에서는 어렵다는 것이다. Exception객체 자체를 재정의하고 메시지처리 로직과 메서드를 넣어두기도 하였으나 최초 발생시킨 Exception을 잃어버리기 때문이다.
Exception으로 메시지 처리를 해결하려는 생각은 자바서비스넷의 JDF개발자인 이원영 님의 글을 언급하고자 한다.
************************************************************************
1. Message는 Exception과는 다르다.

Exception은 Java에서 Process Flow를 Control하기 위한 막강한 수단이다.
C언어에서의 Process Flow는 if condition 과 같은 논리적이고 절차적인 방법으로 Flow를 Control하는 반면 Java언어는 보다 정상적인 Flow를 먼저 생각하고, 그 이외의 예외적인 Flow를 가로채어 처리하는 방식을 취한다.
또한 Exception은 프로그래밍적인 Error상황만을 처리하도록 국한되지 않는다.
Business적인 예외상황이 발생하였을 때 이를 SomeBusinessException으로 그 Process Flow를 Control할 때 막강한 힘을 발휘한다.
Business 적인 예외상황을 Java의 Exception을 활용하여 프로그램으로 구현하는 부분은 다음 문서에서 자세히 기술하겠다.

여기서 얘기 하고 싶은 것은 그러한 용도의 Exception 클래스와 사용자에게 보여져야 할 Message가 항상 1-1 매핑이 되지 않는다는 것이다. 따라서 어떤 Exception 클래스에 항상 어떤 사용자 메시지를 담아두려는 시도는 바람직하지 않다. 그 Exception 클래스에 담겨야 할 내용은 그 Exception이 발생한 간략한 이유를 담는 것이 전부이며 그 Exception의 Type자체가 이미 이유를 알려주고 있다. 그 간략한 이유는 사용자에게 보여져야 할 내용이 아니다.
Exception 클래스는 Message를 담는 그릇이 아니다 !!
***********************************************************************

그렇다고 해서 처음 생각을 접은 것은 아니다. JDK1.4에서는 Exception에 새로이 추가된 생성자가 있는데 Exception(String message, Throwable cause)과 Exception(Throwable cause) 이다. 이를 통해 최초에 발생시킨 Exception을 가져올 수 있다. 그렇다면 Exception을 이용한 메시지 처리가 충분히 가능하지 않겠는가? 그리고 개개 code에서 try, catch의 사용을 줄일 수 있을 것이다.

* 참고 문서
Chained Exception Facility
-----------------------------------------------------------------------

It is common for Java code to catch one exception and throw another:
try {
...
} catch(YourException e) {
throw new MyException();
}

Unfortunately, the information contained in the "causative exception" (YourException in the example above) is generally lost, which greatly complicates debugging. Recognizing this problem, developers sometimes build ad hoc mechanisms to allow certain "wrapped exceptions" to contain a second exception. An accessor is generally provided to extract the contained exception. Such mechanisms are sometimes known as "exception chaining facilities", as they allow arbitrary chains of exceptions to be constructed when contained exceptions are, themselves, wrapped exceptions.
There are many advantages to unifying all of these facilities. Chief among them are: (1) We guarantee that anyone who wants to record the fact that one exception caused another can do so, regardless of what the exceptions are. (2) By providing a common API to record the fact that one exception caused another, we ease this task, making it more likely that programmers will take the trouble to do it. (3) By providing a common API to access causative exceptions, we greatly increase the likelihood that this information will be made available to those who need it. In fact, the proposed mechanism prints entire "causal chain" as part of the standard stack backtrace, ensuring that preexisting programs will provide this information with no additional effort on the part of their authors.

To address these issues, we have added two new methods to Throwable, getCause() and initCause(Throwable), and two new constructors, Throwable(Throwable) and Throwable(String, Throwable). Other "general purpose" exception classes (like Exception, RunTimeException and Error) have been similarly outfitted with (Throwable) and (String, Throwable) constructors. However, even exceptions without such constructors will be usable as "wrapped exceptions" via the initCause method.

The implementation of Throwable.printStackTrace has been modified to display backtraces for the entire causal chain of exceptions. New method getStackTrace provides programmatic access to the stack trace information provided by printStackTrace.

All of the platform's wrapped exceptions will be retrofitted to support the new facility (in addition to their legacy APIs).

The bugtraq report that corresponds to this change is 4209652.

http://java.sun.com/j2se/1.4.1/docs/guide/lang/chained-exceptions.html

-----------------------------------------------------------------------