Framework/예외처리2009. 3. 7. 00:36

좀 더 근원적인 질문을 해보자. 예외란 무엇인가?

인터넷을 검색해보니 정보통신용어사전이라는 쓰잘데기 없는 책에는 예외(exception) : 컴퓨터 시스템의 동작 도중 예기치 않았던 이상 상태가 발생하여 수행 중인 프로그램이 영향을 받는 것. 예를 들면, 연산 도중 넘침에 의해 발생한 끼어들기 등이 여기에 해당한다.
라고 등록되어 있다.

Java 프로그래밍에서의 예외는 조금 다르다. 우선 예외란 이상상태가 아니고 예기치 않았던 상황도 아니다. 프로그래밍에서의 이상 상태는 메모리 누수, 왜곡된 데이타 등을 말하며 예외란 프로그래밍에서 발생하는 일반적인 실행의 흐름(일명 Happy Path)을 바꾸는 몇 가지 조건을 처리하도록 설계한 프로그래밍 언어의 개념을 말하며 이는  주 패스가 아니라 대안적인 패스일뿐 이다. 

예외를 가정할수 있는 몇가지 조건의 종류에는 컴퓨터 하드웨어 문제(충분한 공간의 미확보, 할당하지 못하는 기억장치 접근 등), 운영 체제의 설정 실수, 사용자의 입력 실수(없는 파일, 숫자가 기대되는 곳에 문자 입력), 받아들일수 없는 연산(Devide by Zero) 등 아주 많으며 이러한 상황은 이미 예견되는 상황이다.

갑자기 생뚱맞게 정상적인 교육을 받은 프로그래머라면 대부분 알고 있을 예외 정의를 하는 이유는 자바의 공식 정론은 checked는 정상적인 것이며 runtime exception이 프로그래밍 에러를 가르킨다 를 떠올리기 위해서다.

나는 아주 오랫동안 이 논리에 동의해왔다. 그러나 수십만줄짜리 프로그램들을 작성해 보면서 이는 그닥 실용적이지 않다는 생각을 하게 됐다. 그 주제에 대해 Thinking in Java의 Bruce Eckel와 스프링 개발자로 유명한 Rod Jonson도 이 문제에 대해 언급한 바 있다.  

앞의 글에서 checked exception은 먼저 호출자가 예외를 가지고 현명한 무언가를 할수 있다면 체크되는 예외를 사용해야 한다고 했다. 그런데 여기서 중요한건 가능성이 아니라 빈도와 선택 여부이다. 만약 프레임워크 코드를 작성한다면 호출자가 이 예외를 가지고 현명한 처리를 할지 안할지를 예측할 수는 없다. Java API 코드 설계자도 마찬가지이다. 아직 나중에 누군가의 호출자가 예외 처리를 어떻게 할 것인지를 어떻게 미리 할 수 있을까? 따라서 이 경우에는 가능성이 중요한 선택의 기준이 되고 checked exception에 비중을 많이 두게 된다.

하지만 만약 당신이 지금 호출자의 코드를 작성하고 있다면 즉 그렇게 작성된 자바와 프레임워크를 사용하여 비지니스 코드를 작성하고 있는 중이라면 조금 얘기가 다르다. 일반 프로그램을 작성중이라면 가능성의 문제가 아니라 빈도의 정도가 좀 더 중요해진다. 대부분의 비즈니스 코드에서 Database에 접속할때마다 그리고 SQL을 실행할때마다 그리고 rs.getString("")을 할때마다 SQLException을 잡아서 할 수 있는 무언가는 거의 없다. 비록 Framework에서 모든 코드마다 SQLException을 던진다고 모든 코드에서 그걸 모두 잡거나 다시 던지는건 바보같은 짓이다.

Framework에서는 가능성의 문제로 checked exception을 던지지만 일반 클라이언트 프로그램에서 해당 예외를 잡아서 로그나 화면에 출력하는 것 외에 무언가의 다른 처리를 할 빈도가 거의 없다면 그 많은 checked exception은 생산성의 심각한 장애요소로 작용한다. 예외는 기하급수적으로 퍼지기 때문에 함수 하나에서 throw한 예외는 수천줄의 예외 처리를 감당해야 한다. 그래서 일반 클라인언트 코드에서는 잡아봐자 별로 할것도 없는 checked exception보다는 runtime exception이 좀더 적합하다. 대부분이 로그에 기록하는 것 뿐인 예외 처리라면 맨 처음의 프런트코드에서 한번만 잡아주는게 좋다. 빈도는 낮겠지만 현명한 어떤 처리를 해야 한다면 runtime exception도 catch절로 처리 할수 있기 때문에 상관이 없다.


Framework와 일반 코드에서의 예외 처리 원칙이 이와 같이 다르기 때문에 좀 다른 생각을 해야 할 필요가 있다. 실험실과 비실험실은 그래서 직접 만나는 건 좋지 않다. 사실 비실험실에서 실험실 코드를 직접 호출이 된다는 것 자체가 둘의 경계를 희박하게 만들기 때문에 Facade의 중간 Gate를 만든다. Facade가 해야 할일은 실험실의 캡슐화 역할이 가장 중요하지만 예외를 바꾸는 곳으로도 가장 적절한 곳이다.




위 다이어그램에서 Gate의 역할인 DBController의 코드중 일부인 execQuery를 보면

    public Rows execQuery(IQueryable query, int limit) {
        try {
            long start = System.currentTimeMillis();
            Rows result = query.execQuery(dbm, limit);
            long end = System.currentTimeMillis();

            handleServant(start, end, query, IQueryable.EXEC_QUERY);
            return result;
        } catch(SQLException ex) {
            throw RepositoryException.throwIt(ex, query) ;
        }
    }

실험실 코드에서 던지는 checked exception인 SQLException을 rumtime exception으로 바꾸어 버린다. 대부분의 SQLException이 별도의 Path를 가지지 않는다면 클라이언트는 굳이 처리해야 할 부담을 갖지 않는 것이다. 적은 확률이지만 무언가를 해야 한다면 runtime exception인 RepositoryException을 잡는다. 만약 대부분의 코드가 SQL Exception을 처리해야 한다면 - Framework를 이용하는 새로운 Framework를 만들어야 한다면 - 상속등을 이용해 새로운 Gate를 만들면 그만이다.


이제 예외 처리에 대한 문제를 정리해 보자.

모든 호출자들이 이 문제에 대해 알고 싶어하고 처리해야 하는가 ?
   ex) processInvoice() 메소드에서 소비 한도를 초과했다.
   -> checked를 정의하고 사용하여 자바의 컴파일시 지원의 장점을 사용하자.

호출자들 중의 소수만이 이 문제를 처리하길 원하는가?
   ex) getString() throws SQLException
   -> runtime - 이것은 호출자에게 예외를 잡을지에 대한 선택권을 넘겨주면서도 모든 호출자들에게 그것을 잡도록 강요하지 않는다.

먼가 끔직히 잘못되었는가? 그 문제가 치료 불가능한가?
   ex) DB 접속오류
   -> runtime - 오출자가 그 에러에 관한 정보를 통보 받는것 외에 아무것도 할 수가 없다.

아직 잘 모르겠는가?
   -> runtime 고려 - 가능한 runtime을 고려해본다. 두개의 클래스를 제공해 호출자가 잡을지 말지에 대한 선택권을 줘라.

'Framework > 예외처리' 카테고리의 다른 글

GUI TEST  (0) 2009.06.08
예외 수집기  (0) 2009.03.26
exception framework  (0) 2009.03.10
checked exception의 문제  (0) 2009.02.09
예외 처리 격언  (2) 2009.02.09
Posted by bleujin