Framework/예외처리2009. 2. 9. 05:15


exception란 말 그대로 exception이 필요한 상황은 예외적이라고 생각해서일까?
언어에 대한 기본 문법책은 아주 많이 있지만 exceptional 하게도 exception 분야는 그리 많이 다루지 않는다.

좋은 클래스와 좋은 메소드 디자인과 마찬가지로 좋은 예외처리 디자인은 중요하다. 


관례 1 예외는 예외 상황에만 써야 한다.

// Bad
try {
   int i = 0 ;
   while(true)
       a[i++].fn() ;
} catch(ArrayIndexOutOfBoundsException e) {
}


// Good
for (int i =0 ; i < a.length ; i++)
   a[i].fn() ;

예외 기반의 구현 패턴은 코드의 목적을 애매하게 만들고 성능도 떨어뜨린다. 게다가 제대로 동작하리란 보장조차 없다. 예외 기반의 구현패턴을 쓰면, 다른 버그가 있어도 프로그램은 이 버그를 감춘채 조용히 수행된다. 이런 버그는 정말 잡아내기 어렵다. 예외는 말 그대로 예외상황에서만 써야 한다.프로그램 흐름을 예외로 제어하려 하면 안된다. 


관례 2 checked exception과 runtime exception을 구분해서 던져라

처리해야 하는 예외(checked exception)는 호출자가 예외 상항을 복구 할수 있다가 기대할 수 있을때 던진다. 보통의 경우 런타임 예외는 프로그래밍 오류(precondition violation)가 발생했을때만 써야 한다. 에러는 JVM의 자원이 부족하거나, 불변규칙이 변하는 것과 같이 더 이상 프로그램을 진행할 수 없을때 JVMㅡ에서만 던지는 것이 관례이다.

그러나 관례란 관례일 뿐이다. 관례 2는 많은 예외상항이 있는데 그것에 관해서는 따로 다른 글로 포스팅하겠다.



관례 3 예외를 던질때는 신중해야 한다.

# Bad
} catch(TheCheckedException e) {
   throw new Error("Assertion error") ;
}


# BadBad
} catch(TheCheckedException e){
   e.printStackTrace() ;
   System.exit(1) ;
}

# Not Bad
if(obj.actionPermitted(args)) {
   obj.action(args) ;
} else {
   // 예외 상황을 처리한다.
}


처리해야 하는 예외를 던지는 메소드가 너무 많은 API는 아주 쓰기 번거롭다. 처리해야 하는 예외를 던지는 메소드를 호출하는 메소드는 catch 블록을 써서 이 예외를 잡거나, 같은 예외를 던진다고 선언하여 이 예외를 외부로 전파해야 한다. 사실 이런 작업은 프로그래머에게는 만만치 않은 부담을 준다. API를 정확히 쓰더라도 이런 예외가 발생할 수 있고, 프로그래머가 이 예외를 적절하게 처리할 수 있을때만 처리할 수 있는 예외를 던져야 한다. 위의 2조건을 모두 만족하지 못한다면 처리하지 않는 예외를 던지는 것이 좋다. 이 메소드를 사용하는 프로그래머들이 어떻게 처리할까 라는 질문을 한번 더 던져보자....

사실 관례 2의 예외는 바로 이 관례 3에 기인한다. 지나치기 많은 예외를 던지는것은 부작용을 너무 많이 가져온다. 



관례 4 표준 예외를 써라

클래스의 이름이 중요한만큼이나 예외의 이름도 중요하다. 따라서 가능하면 존재하는 예외라면 존재하는 표준 예외를 써주는 것이 좋다.

IllegalArgumentException : 인자값이 적절하지 못할때
IllegalStateExcetpion : 호출을 받은 객체 상태가 적절하지 못할때
NullPointerException : null을 금지한 인자값이 null일때
IndexOutOfBoundsException : 인덱스 값이 범위를 벗어났을때
ConcurrentModificationException : 동시 수정을 금지한 객체를 동시에 수정할때.
UnsupportedOperationException : 객체가 메소드를 지원하지 않을때.


관례 5 예외를 적절하게 추상화하라

// exception translation
try {

   ......
} catch(LowerLevelException e){
   throw new HigherLevelException(...) ;
}

낮은 계층의 세부 구현사항이 외부에 드러나게 되어 추상화 수준이 높은 계층의 API를 오염시킬수 있다. 인터페이스 디자인할때 어려운 점중 하나는 어느 수준의 예외를 던져야 하는가이다. 메소드 구현이 되지 않는 상태에서 어떤한 예외가 나올것이라고 추측하는 것은 미래를 예측하는 것 만큼이나 매우 매우 어렵기 때문이다. 그래서 인터페이스에서 아무 예외든 다 받아주겠어 식의 throw Exception은 다시 관례 2의 예외로 나타난다. 



관례 6 실패에 대한 자세한 정보를 상세 메시지 문자열에 담아라

예외의 문자열 표현과 최종 사용자가 쉽게 이해할 수 있어야 하는 "사용자에게 제공하는 오류 메시지"와 혼동하면 안된다. 사용자에게 제공하는 오류 메시지와 달리, 예외의 문자열 표현은 프로그래머나 유지보수 요원이 실패를 분석할때 쓰는 것이다. 따라서 예외ㅡ 문자열 표현은 이해하기 쉬운 정보를 담기보다는 자세한 정보를 담는 것이 훨씬 더 중요하다.


# Bad
try {
    ....
} catch(IOException ex) {
   throw new UserException("예외 발생.") ;
}


# Good
try {
    ....
} catch(IOException ex) {
   throw new UserException( ex.getMessage() + "예외와 관계있을수 있는 프로퍼티 정보 ") ;



관례 7 실퍠 원자성을 얻기 위해 노력하라

# Good
public Object pop(){
   if (size == 0) throw new EmptyStackException() ;

   Object result = element[--size] ;
   elements[size] = null ;
   return result ;
}



관례 8 예외를 잡아서 버리지 마라

예외를 잡아서 버리지 마라, 너무나 당연한 진리를 많은 프로그래머가 무시하고 있기 때문에 다시 한번 이야기한다. 어떤 메소드가 예외를 던질 수 있다는 것은, 이 메소드의 설계자가 여러분께 뭔가 알리고 싶어한다는 것이다.

catch 블럭 안에서 정말 아무것도 할 것이 없다면, 최소한 왜 예외를 잡어서 처리하지 않고 버리는지 그 이유라도 주석으로 달아 놓아야 한다.

그러나 이런 관례는 오히려 프로그래머의 게으름을 촉진시킬수 있다.




예외의 예외는 다음 글에서 =ㅅ=;




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

GUI TEST  (0) 2009.06.08
예외 수집기  (0) 2009.03.26
exception framework  (0) 2009.03.10
checked vs runtime  (0) 2009.03.07
checked exception의 문제  (0) 2009.02.09
Posted by bleujin