한 컴포넌트가 다른 컴포넌트와 닿는 접점으로 아주 흔하게 사용되는 것 중 하나가 바로 리스너 패턴입니다. 커플링도 적은 편이고, 이해하기도 쉬운 편이지요. 이클립스 같이 전사적인 블라인드 협력이 이뤄지는 구조에서는, 이러한 접점들을 안전하게 관리하는 것이 매우 중요합니다.

원칙1. 당신의 리스너가 완벽할 것이라는 기대를 버리시라.

그렇습니다. 우리는 인간이기 때문에, 거의 반드시라고 해도 좋을 정도로 실수를 하곤 합니다. 대부분의 실수는 지역적인 장해만을 발생시키지만, 컴포넌트의 접점에 문제가 발생할 경우, 그 지점과 연관된 모든 부분이 엉망이 되어 버릴 수 있으며, 심각 한 경우 사용자가 작업을 더 이상 진행 할 수 없게 되어버리기도 합니다. 그러므로 컴포넌트 접점은 좀 더 각별한 주의를 기울여 보호할 필요가 있습니다.

for(int i=0; i<100; i++){
	doSomething(i);
}

위의 코드를 예로 들어 봅시다. 만약 doSomething이라는 메서드가 i가 17일 때 문제를 일으켜 익셉션을 던진다고 가정 해 봅시다. 이 경우 정상적인 작업 83개가 17번 작업 때문에 모두 수행되어버리지 않게 됩니다.

이벤트를 제공하는 컴포넌트들은 여러개의 리스너를 가지고 있습니다. 만약 한 리스너가 문제를 일으킨다면, 아직 이벤트를 공급 받지 못한 나머지 모든 리스너들은 제대로 반응하지 못할 것이고, 이 후 같은 이벤트 통지역시 모두 마비 되어버리고 맙니다. 이 문제를 해결하기 위해서, 저는 불신과 배척이라는 테크닉을 이용합니다.

불신과 배척

불신과 배척의 철학은 아주 간단합니다. 기본적으로 리스너를 믿지 않으며, 문제를 일으킨 놈은 퇴출시켜 버립니다. 자 그 구현을 볼까요?

private Set<Listener> listeners;

private void fireEvent(Event e){
  Listener[] listenerArray = listeners.toArray(new Listener[listeners.size()]);

  for(Listener eachListener : listenerArray){
    try{
      eachListener.eventOccured(e);
    catch(Exception e){
      listeners.remove(eachListener);
      debug(eachListener + " 말썽쟁이를 쫒아냈어요.");
    }
  }
}

자 이제 한 줄 씩 살펴 볼까요? 우선 리스너 세트를 바로 이터레이트 하지 않고, 배열에 담은 다음 이터레이션 했습니다. 그 이유는 간단 합니다. 만약 리스너가 이 이벤트로 인해, 자기 자신을 리스너 그룹에서 제외하거나, 다른 리스너를 참가시킬 경우, 컨커런시 문제가 발생하기 때문이죠. 이미 for 문이 참조중인 컬렉션이 for 문이 도는 도중 변경 되어버리면 for 문은 어쩔 줄 몰라하게 되겠죠?

다음은 이터레이션 내부를 봅시다. try 로 모든 개별 리스너에게 통지할 때마다 문제를 감지하는 군요. 그렇다면 한 리스너가 문제를 일으킨다 하더라도, 다른 리스너들이 피해를 입지 않겠네요. 게다가 문제를 일으킨 녀석은 리스너 그룹에서 빼 버리는 군요. 저는 그저 추방했지만, 몇 번 더 기회를 줘 본다던지와 같은 재미있는 대응도 가능하겠죠?

장해 복구

그런데 이벤트에 대응하는 리스너가 UI를 변경하는 일은 흔히 있는 일입니다. 리스너의 개발자는 자기 자신이 UI 스레드 내에서만 호출될 거라며 바보같은 고집을 피우고 있을 지도 모릅니다. 하지만 정말 예상하지 못하게도, 다른 스레드에서 이벤트가 발생하는 경우는 종종 있습니다. 이렇게 흔히 발생할 수 있는 오류에 대해서는 다음과 같은 대응도 시도 해 볼 수 있습니다. 만약 발생한 예외의 종류가 InvalidThreadAccessException 이라면, UI 스레드에 동기화 하여, 이벤트를 한 번 더 전달 해 줘 보는 것이지요. 물론, 상태가 중복적으로 수정된다던가 하는 문제가 발생할 수도 있습니다. 도전과 실험은 여러분의 몫입니다.

결론

이클립스는 수많은 블라인드 협력을 통해 운영되는 독특한 공간 입니다. 자신이 실수를 하지 않았다고 해서, 자신의 클라이언트가 실수를 했다고 해서, 마비되는 컴포넌트를 작성해서는 결코 안되며, 좋은 이클립스 개발자가 되려면 이러한 경우 자신의 잘못으로 인정할 수 있는 반성이 필요합니다. 엔드유저가 격게될 피해를 최소화하는 것은 무엇보다 중요한 일입니다. 자신의 코드를 잘 만드는 것도 중요하지만, 협력의 준비가 되지 않은 코드는 아무리 우수하더라도 많은 이의 골칫거리가 될 뿐입니다.

Posted by 지이이이율
,