Posts RxJava - 디버깅
Post
Cancel

RxJava - 디버깅

RxJava의 디버깅 문제점과 대안

  • RxJava 프로그래밍은 데이터를 생성 및 통지하고 이를 구독하여 처리하는 과정이 하나의 문장으로 되어 있다.

  • 즉, RxJava 프로그래밍은 선언적 프로그래밍 방식이기때문에 데이터의 상태 변화를 확인하기 위한 디버깅이 쉽지 않다.

  • RxJava 프로그래밍은 여러 쓰레드가 동시에 실행되는 비동기 프로그래밍이기때문에 실행 시, 항상 같은 결과가 나온다는 보장을 할 수가 없다.

  • 이러한 문제점을 해결하기 위해 RxJava에서는 doXXX로 시작하는 함수를 통해 생산자나 소비자쪽에서 이벤트 발생 시, 로그를 기록할 수 있는 방법을 제공한다.

  • 함수형 프로그래밍의 특성상 부수 효과는 소비자쪽에서 처리하는것이 맞지만 doXXX 함수는 예외이다.

  • 따라서 소비자가 전달 받은 데이터를 처리하기 전 원본 데이터의 상태나 변환 및 필터링 등으로 가공되는 시점의 데이터 상태를 doXXX 함수를 통해 쉽게 파악할 수 있다.

디버깅을 위한 doXXXX 함수

doOnSubscribe

  • 구독 시작 시, 지정된 작업을 처리할 수 있다.

  • onSubscribe 이벤트가 발생하기 직전에 실행된다.

doOnSubscribe

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 이벤트가 발생하기 직전에 실행된다.

  • 통지된 데이터가 함수형 인터페이스의 파라미터로 전달되므로 통지 시점마다 데이터의 상태를 확인할 수 있다.

doOnNext

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 이벤트가 발생하기 직전에 실행된다.

doOnComplete

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 이벤트가 발생하기 직전에 실행된다.

  • 통지된 에러 객체가 함수형 인터페이스의 파라미터로 전달되므로 에러 상태를 확인할 수 있다.

doOnError

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 객체를 함수형 인터페이스의 파라미터로 전달 받아서 처리한다.

doOnEach

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

  • 소비자가 구독을 해지하는 시점에, 지정된 작업을 처리할 수 있다.

  • 완료나 에러로 종료 될 경우에는 실행되지 않는다.

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부를 공부하고 작성한 글입니다.
강의영상 링크

This post is licensed under CC BY 4.0 by the author.

RxJava - 스케쥴러의 종류(2)

RxJava - 테스트(1)

Comments powered by Disqus.