brunch

You can make anything
by writing

C.S.Lewis

[RxJava] #9 예외처리

들어가는 글: 오늘은 RxJava로 예외 처리하는 방법에 대해 알아보도록 하겠습니다. 가벼운 마음으로 기존의 자바 개발자들이 겪을 수 있는 혼란을 줄일 수 있는 방향으로 말씀드릴께요. 


1. Java 언어의 예외 처리 


1995년 자바 언어가 처음 나왔을 때 예외 처리는 try catch를 활용한 기법을 도입하였습니다. C언어의 경우 정상적인 코드와 예외 처리 코드가 뒤섞여 있어서 무엇이 원저자의 의도인지 알기 어려웠거든요. 


보통 가장 전통적인 예외 처리 방식은 예외 코드였습니다. 


int ERROR_UNKNOWN = -999; 


간단히 말하면 이런 것입니다. 자바 언어에서는 이것을 명확하게 하기 위하여 try catch finally 구문을 도입합니다. checked 예외 처리가 기본으로 들어가는 파일 입출력 코드를 살펴봅니다. 


try { 
       BufferedReader br = new BufferedReader(new FileReader("doc/a.txt")); 
       ... //나머지 IO 코드 
} catch (FileNotFoundException e) {
       ... 
} finally {
     ...

try / catch 문은 C++ 언어에도 그대로 사용됩니다. 그야말로 객체 지향 언어의 표준적인 예외 처리 방식입니다. 한편 RuntimeException같이 unchecked 예외는 명시적으로 처리하지 않아도 됩니다. 


2. RxJava의 예외 처리 


RxJava의 예외 처리는 먼저 subscribe()할 때 가장 먼저 할 수 있습니다. 

간단한 예제 코드를 살펴봅니다. 


Observable source = Observable.from(1, 0); 
source.subscribe(v -> System.out.println("value = " + v, //onNext 이벤트
    err -> System.err.println("error = " + err.getMessage());  //onError 이벤트 


가장 간단한 예는 onError 이벤트에 예외 처리 코드를 넣으면 됩니다. 

즉, 자바 언어에서 기본적으로 제공하는 try catch를 활용할 수 없습니다. 


헉... 이때부터 자바 개발자의 혼란은 시작됩니다. 


3. try / catch는 완전히 못쓰나?


반드시 그런 것은 아닙니다. 아래의 코드도 동작은 합니다. 


Observable.just(1,0)
.map(v -> {
    try { 
         return 100 / v;
    } catch (ArithmeticException e) {
         throw e;
    }
}).subscribe(v -> System.out.println("value = " + v),  
                       e -> e.printStackTrace());


위의 내용을 실행해보면 

value = 100 
java.lang.ArithmeticException: / by zero value = -1


이렇게 나옵니다. 다시 2차 맨붕이 시작됩니다. 그러면 어떻게 하라는 거지? 


3. RxJava로 예외 처리 해보기 


첫째, RxJava에서는 try catch를 쓰는 것을 권장하지 않습니다. Observable은 일종의 데이터 흐름을 지향하기 때문에 onError로 보내는 것은 데이터 흐름이 치명적으로 손상되어 복구할 수 없을 때를 의미합니다. 즉, 최후의 카드입니다. 


공식 문서에서는 onError 이벤트를 정상 흐름으로 복구(restore)하라고 나옵니다. 

https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators 


1) swallow the error and switch over to a backup Observable to continue the sequence

2) swallow the error and emit a default item

3) swallow the error and immediately try to restart the failed Observable

4) swallow the error and try to restart the failed Observable after some back-off interval


번역을 해보면 

1) onError를 삼키고 흐름을 계속 할 수 있는 백업 Observable로 교체하라 

2) onError를 삼키고 기본 값을 발행하라 

3) onError를 삼키고 즉시 실패한 Observable을 다시 시작하라 

4) onError를 삼키고 일정 시간(back-off interval)을 기다렸다가 실패한 Observable을 재시작하라 


무슨 얘기일까요? 저도 처음에는 무지 햇갈렸습니다. 

일단 onError 이벤트를 subscribe()에서 처리하면 되지? 왜 위와같은 전략을 써야 할까요? 


조금만 생각해보세요~ 


정답은... 예외(onError 이벤트)는 수많은 곳에서 발생할 수 있다.. 입니다. 

위의 예제는 map() 함수 한 개지만 보통 Observable을 구성하기 위해서는 flatMap(), zip(), filter() 등 수많은 함수를 활용하게 됩니다. 따라서 가장 말단 흐름에 있는 subscribe()로는 다 감당할 수가 없습니다. 


자바 언어에서 제공하는 try catch 만큼도 오류 처리를 세분화할 수 없는 것이죠. 

그래서.. 그런 것들을 도와줄 수 있는 함수(리액티브 연산자)들을 필요로 하게 됩니다. 


10화에서는 위에서 말한 4가지 전략에 어울리는 Operators 에 대해서 간단히 알아보도록 하겠습니다. 

감사합니다. 

 

나오는 글:  어쨋든 예전에 비해서 좀더 덜 답답한 마음으로 RxJava에 대해 말씀드릴 수 있어서 기분이 좋네요. 역시 기술은 시간을 가지고 배우게 되면 다 이해하게 되어 있나봅니다. RxJava는 참 공부하기 어려웠어요 ㅋ 


10화에서 뵙겠습니다. 

감사합니다. 


2017.7.9

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari