RxJava의 디버깅 문제점과 대안
RxJava 프로그래밍은 데이터를 생성 및 통지하고 이를 구독하여 처리하는 과정이 하나의 문장으로 되어 있다.
즉, RxJava 프로그래밍은 선언적 프로그래밍 방식이기때문에 데이터의 상태 변화를 확인하기 위한 디버깅이 쉽지 않다.
RxJava 프로그래밍은 여러 쓰레드가 동시에 실행되는 비동기 프로그래밍이기때문에 실행 시, 항상 같은 결과가 나온다는 보장을 할 수가 없다.
이러한 문제점을 해결하기 위해 RxJava에서는 doXXX로 시작하는 함수를 통해 생산자나 소비자쪽에서 이벤트 발생 시, 로그를 기록할 수 있는 방법을 제공한다.
함수형 프로그래밍의 특성상 부수 효과는 소비자쪽에서 처리하는것이 맞지만 doXXX 함수는 예외이다.
따라서 소비자가 전달 받은 데이터를 처리하기 전 원본 데이터의 상태나 변환 및 필터링 등으로 가공되는 시점의 데이터 상태를 doXXX 함수를 통해 쉽게 파악할 수 있다.
디버깅을 위한 doXXXX 함수
doOnSubscribe
구독 시작 시, 지정된 작업을 처리할 수 있다.
onSubscribe 이벤트가 발생하기 직전에 실행된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* onSubscribe 이벤트 발생 전에 호출되는 doOnSubscribe 의 사용 예제
*/
public class DoOnSubscribeExample {
public static void main(String[] args) {
Observable.just(1, 2, 3, 4, 5, 6, 7)
.doOnSubscribe(disposable -> Logger.log(LogType.DO_ON_SUBSCRIBE, "# 생산자: 구독 처리 준비 완료"))
.subscribe(
data -> Logger.log(LogType.ON_NEXT, data),
error -> Logger.log(LogType.ON_ERROR, error),
() -> Logger.log(LogType.ON_COMPLETE),
dispose -> Logger.log(LogType.ON_SUBSCRIBE, " # 소비자: 구독 처리 준비 완료 알림 받음")
);
}
}
/*
doOnSubscribe() | main | 23:31:27.008 | # 생산자: 구독 처리 준비 완료
onSubscribe() | main | 23:31:27.016 | # 소비자: 구독 처리 준비 완료 알림 받음
onNext() | main | 23:31:27.016 | 1
onNext() | main | 23:31:27.017 | 2
onNext() | main | 23:31:27.017 | 3
onNext() | main | 23:31:27.017 | 4
onNext() | main | 23:31:27.017 | 5
onNext() | main | 23:31:27.017 | 6
onNext() | main | 23:31:27.017 | 7
onComplete() | main | 23:31:27.018
*/
doOnNext
생산자가 데이터를 통지하는 시점에, 지정된 작업을 처리할 수 있다.
onNext 이벤트가 발생하기 직전에 실행된다.
통지된 데이터가 함수형 인터페이스의 파라미터로 전달되므로 통지 시점마다 데이터의 상태를 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 데이터 통지 시 마다 실행되는 doOnNext 를 이용해 데이터의 상태를 확인하는 예제
*/
public class DoOnNextExample {
public static void main(String[] args) {
Observable.just(1, 3, 5, 7, 9, 10, 11, 12, 13)
.doOnNext(data -> Logger.log(LogType.DO_ON_NEXT, " 원본 통지 데이터: " + data))
.filter(data -> data < 10)
.doOnNext(data -> Logger.log(LogType.DO_ON_NEXT, "# filter 적용 후: " + data))
.map(data -> "#### " + data + " ####")
.doOnNext(data -> Logger.log(LogType.DO_ON_NEXT, "# map 적용 후: " + data))
.subscribe(data -> Logger.log(LogType.ON_NEXT, "# 최종 데이터: " + data));
}
}
/*
doOnNext() | main | 00:45:22.943 | 원본 통지 데이터: 1
doOnNext() | main | 00:45:22.945 | # filter 적용 후: 1
doOnNext() | main | 00:45:22.945 | # map 적용 후: #### 1 ####
onNext() | main | 00:45:22.945 | # 최종 데이터: #### 1 ####
doOnNext() | main | 00:45:22.946 | 원본 통지 데이터: 3
doOnNext() | main | 00:45:22.946 | # filter 적용 후: 3
doOnNext() | main | 00:45:22.946 | # map 적용 후: #### 3 ####
onNext() | main | 00:45:22.946 | # 최종 데이터: #### 3 ####
doOnNext() | main | 00:45:22.946 | 원본 통지 데이터: 5
doOnNext() | main | 00:45:22.946 | # filter 적용 후: 5
doOnNext() | main | 00:45:22.946 | # map 적용 후: #### 5 ####
onNext() | main | 00:45:22.946 | # 최종 데이터: #### 5 ####
doOnNext() | main | 00:45:22.946 | 원본 통지 데이터: 7
doOnNext() | main | 00:45:22.946 | # filter 적용 후: 7
doOnNext() | main | 00:45:22.947 | # map 적용 후: #### 7 ####
onNext() | main | 00:45:22.947 | # 최종 데이터: #### 7 ####
doOnNext() | main | 00:45:22.947 | 원본 통지 데이터: 9
doOnNext() | main | 00:45:22.947 | # filter 적용 후: 9
doOnNext() | main | 00:45:22.947 | # map 적용 후: #### 9 ####
onNext() | main | 00:45:22.947 | # 최종 데이터: #### 9 ####
doOnNext() | main | 00:45:22.947 | 원본 통지 데이터: 10
doOnNext() | main | 00:45:22.947 | 원본 통지 데이터: 11
doOnNext() | main | 00:45:22.947 | 원본 통지 데이터: 12
doOnNext() | main | 00:45:22.947 | 원본 통지 데이터: 13
*/
doOnComplete
생산자가 완료를 통지하는 시점에, 지정된 작업을 처리할 수 있다.
onComplete 이벤트가 발생하기 직전에 실행된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* onComplete 이벤트 발생 전에 호출되는 doOnComplete 의 사용 예제
*/
public class DoOnCompleteExample {
public static void main(String[] args) {
Observable.range(1, 5)
.doOnComplete(() -> Logger.log(LogType.DO_ON_COMPLETE, "# 생산자: 데이터 통지 완료"))
.subscribe(
data -> Logger.log(LogType.ON_NEXT, data),
error -> Logger.log(LogType.ON_ERROR, error),
() -> Logger.log(LogType.ON_COMPLETE)
);
}
}
/*
onNext() | main | 00:55:58.177 | 1
onNext() | main | 00:55:58.180 | 2
onNext() | main | 00:55:58.180 | 3
onNext() | main | 00:55:58.180 | 4
onNext() | main | 00:55:58.180 | 5
doOnComplete() | main | 00:55:58.180 | # 생산자: 데이터 통지 완료
onComplete() | main | 00:55:58.181
*/
doOnError
생산자가 에러를 통지하는 시점에, 지정된 작업을 처리할 수 있다.
onError 이벤트가 발생하기 직전에 실행된다.
통지된 에러 객체가 함수형 인터페이스의 파라미터로 전달되므로 에러 상태를 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* onError 이벤트 발생 전에 호출되는 doOnError 의 사용 예제
*/
public class DoOnErrorExample {
public static void main(String[] args) {
Observable.just(3, 6, 7, 12, 15, 20)
.zipWith(Observable.just(1, 2, 3, 4, 0, 5), (a, b) -> a / b)
.doOnError(error -> Logger.log(LogType.DO_ON_ERROR, "# 생산자: 에러 발생 - " + error.getMessage()))
.subscribe(
data -> Logger.log(LogType.ON_NEXT, data),
error -> Logger.log(LogType.ON_ERROR, error)
);
}
}
/*
onNext() | main | 01:06:17.259 | 3
onNext() | main | 01:06:17.263 | 3
onNext() | main | 01:06:17.263 | 2
onNext() | main | 01:06:17.263 | 3
donOnError() | main | 01:06:17.264 | # 생산자: 에러 발생 - / by zero
onERROR() | main | 01:06:17.265 | java.lang.ArithmeticException: / by zero
*/
doOnEach
doOnNext, doOnComplete, doOnError를 한번에 처리할 수 있다.
Notification 객체를 함수형 인터페이스의 파라미터로 전달 받아서 처리한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* doOnEach 를 이용해 doOnNext, doOnComplete, doOnError를 한꺼번에 처리하는 예제
*/
public class DoMOnEachExample {
public static void main(String[] args) {
Observable.range(1, 5)
.doOnEach(notification -> {
if (notification.isOnNext())
Logger.log(LogType.DO_ON_NEXT, "# 생산지: 데이터 통지 - " + notification.getValue());
else if (notification.isOnError())
Logger.log(LogType.DO_ON_ERROR, "# 생산지: 에러 발생 - " + notification.getError());
else
Logger.log(LogType.DO_ON_COMPLETE, "# 생산지: 데이터 통지 완료");
})
.subscribe(
data -> Logger.log(LogType.ON_NEXT, data),
error -> Logger.log(LogType.ON_ERROR, error),
() -> Logger.log(LogType.ON_COMPLETE)
);
}
}
/*
doOnNext() | main | 01:43:59.112 | # 생산지: 데이터 통지 - 1
onNext() | main | 01:43:59.117 | 1
doOnNext() | main | 01:43:59.117 | # 생산지: 데이터 통지 - 2
onNext() | main | 01:43:59.118 | 2
doOnNext() | main | 01:43:59.118 | # 생산지: 데이터 통지 - 3
onNext() | main | 01:43:59.118 | 3
doOnNext() | main | 01:43:59.118 | # 생산지: 데이터 통지 - 4
onNext() | main | 01:43:59.119 | 4
doOnNext() | main | 01:43:59.119 | # 생산지: 데이터 통지 - 5
onNext() | main | 01:43:59.119 | 5
doOnComplete() | main | 01:43:59.119 | # 생산지: 데이터 통지 완료
onComplete() | main | 01:43:59.120
*/
doOnCancel / doOnDispose
소비자가 구독을 해지하는 시점에, 지정된 작업을 처리할 수 있다.
완료나 에러로 종료 될 경우에는 실행되지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 구독 해지 시 doOnDispose 를 이용하여 지정한 처리를 하는 예제
*/
public class DoOnDisposeExample {
public static void main(String[] args) {
Observable.fromArray(SampleData.carMakers)
.zipWith(Observable.interval(300L, TimeUnit.MILLISECONDS), (carMaker, num) -> carMaker)
.doOnDispose(() -> Logger.log(LogType.DO_ON_DISPOSE, "# 생산자: 구독 해지 완료"))
.subscribe(new Observer<CarMaker>() {
private Disposable disposable;
private long startTime;
@Override
public void onSubscribe(Disposable disposable) {
this.disposable = disposable;
this.startTime = TimeUtil.start();
}
@Override
public void onNext(CarMaker carMaker) {
Logger.log(LogType.ON_NEXT, carMaker);
if(TimeUtil.getCurrentTime() - startTime > 1000L){
Logger.log(LogType.PRINT, "# 소비자: 구독 해지 , 1000L 초과");
disposable.dispose();
}
}
@Override
public void onError(Throwable error) {
Logger.log(LogType.ON_ERROR, error);
}
@Override
public void onComplete() {
Logger.log(LogType.ON_COMPLETE);
}
});
TimeUtil.sleep(2000L);
}
}
/*
onNext() | RxComputationThreadPool-1 | 02:07:21.612 | CHEVROLET
onNext() | RxComputationThreadPool-1 | 02:07:21.854 | HYUNDAE
onNext() | RxComputationThreadPool-1 | 02:07:22.156 | SAMSUNG
onNext() | RxComputationThreadPool-1 | 02:07:22.456 | SSANGYOUNG
print() | RxComputationThreadPool-1 | 02:07:22.456 | # 소비자: 구독 해지 , 1000L 초과
doOnDispose() | RxComputationThreadPool-1 | 02:07:22.457 | # 생산자: 구독 해지 완료
*/
기타 디버깅을 위한 doXXXX 함수
doAfterNext : 생산자가 통지한 데이터가 소비자에 전달된 직후 호출되는 함수
doOnTerminate : 완료 또는 에러가 통지 될 때 호출 되는 함수 (doOnComplete + doOnError)
doAfterTerminate : 완료 또는 에러가 통지된 후 호출 되는 함수 (after doOnComplete + doOnError)
doFinally : 구독이 취소 된 후, 완료 또는 에러가 통지된 후 호출되는 함수 (doOnDispose/doOnCancel + doOnComplete + doOnError)
doOnLifecycle : 소비자가 구독할 때 또는 구독 해지할 때 호출되는 함수 (doOnSubscribe + doOnDispose/doOnCancel)
이 글은 inflearn에 있는 Kevin의 알기 쉬운 RxJava 2부를 공부하고 작성한 글입니다.
강의영상 링크