Где размещать проверки параметров в реактивных потоках?

Сегодня в офисе мы с коллегами обсуждали важность того, чтобы вычисления выполнялись декларативно внутри реактивного потока, а не жадно, а императивно при вызове метода.

Для пояснения приведу очень наивный, но наглядный пример. Мы сказали, что следующее будет неправильным:

Single<Integer> getStream(String s) {
   Integer n = Integer.valueOf(s);
   return Single.just(n);
}

Очевидно, пользователь метода сделал бы:

getStream("A").returnOnError(error -> 0)
              .subscribe(System.out::println, System.err::println);

Но этот код с готовностью потерпит неудачу в getStream("A"), и мой обработчик ошибок потока никогда не будет достигнут. Пользователь метода реактивного потока не ожидает выполнения попытки отлова вокруг его вызова, поскольку поток предлагает другие механизмы для обработки ошибок, верно?

Итак, мы предложили сделать одно из следующих действий:

Single.defer(() -> Single.just(Integer.valueOf(s));
Single.fromCallable(() -> Integer.valueOf(s))
Sigle.create(subscriber -> { subscriber.onNext(Integer.valueOf(s)); }

Но затем, в следующем обзоре кода я нашел что-то вроде этого:

Single<Foo> getReactiveFoo(Bar bar, Baz baz, int price) {
     return Single.fromCallable(() -> {
           Objects.requireNonNull(bar, "bar should not be null");
           Objects.requireNonNull(baz, "baz should not be null");
           Preconditions.checkArgument(price > 0, "price should be > 0");
           //...
     });
}

Итак, теперь я нашел это довольно интересным, потому что я всегда считал, что этот тип проверки для NullPointerException, IllegalArgumentException, IndexOutOfBoundException и т. д. имеет целью зафиксировать ошибки программиста, и я обычно предпочитаю, чтобы они терпели неудачу как можно ближе к месту, где возникает ошибка. Когда вы бросаете любой из них, вы, безусловно, хотите, чтобы программа немедленно вышла из строя и сигнализировала, что есть ошибка, которую необходимо обработать, и я не возражал бы против того, чтобы это произошло с нетерпением и как можно ближе к тому месту, где ошибка была введена.

Поэтому, даже несмотря на наше первоначальное обсуждение, у меня было ощущение, что эти проверки ошибок программиста принадлежат вне потока и в нетерпеливом вызове метода, т.е.

Single<Foo> getReactiveFoo(Bar bar, Baz baz, int price) {
     Objects.requireNonNull(bar, "bar should not be null");
     Objects.requireNonNull(baz, "baz should not be null");
     Preconditions.checkArgument(price > 0, "price should be > 0");
     return Single.fromCallable(() -> {
           //...
     });
}

Я надеюсь, что это не самоуверенный вопрос. Я хотел бы понять, есть ли у сообщества реактивных потоков мнение о том, как именно мы должны поступать с этим типом исключений и относятся ли они к декларативному реактивному потоку или вызову императивного метода.

Возможно, есть хорошие примеры или справочные материалы по теме, которые вы могли бы предложить мне прочитать.


person Edwin Dalorzo    schedule 13.03.2018    source источник
comment
Посмотрите, что делает RxJava с точки зрения проверки ввода: github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/   -  person akarnokd    schedule 13.03.2018
comment
Итак, теперь я нашел это довольно интересным, потому что я всегда считал, что этот тип проверки для NullPointerException, IllegalArgumentException, IndexOutOfBoundException и т. д. имеет намерение зафиксировать ошибки программиста, и я обычно предпочитаю, чтобы они терпели неудачу как можно ближе к месту, где возникает ошибка. Когда вы бросаете любой из них, вы, безусловно, хотите, чтобы программа немедленно вышла из строя и сигнализировала, что есть ошибка, которую необходимо обработать, и я не возражал бы против того, чтобы это произошло с нетерпением и как можно ближе к тому месту, где ошибка была введена. Соглашаться.   -  person Zsolt Safrany    schedule 26.08.2019


Ответы (1)


Я думаю, что разумно рассматривать такую ​​проверку как предварительные условия, и поэтому их невыполнение можно считать ошибкой программирования. Они должны отказывать быстро и сильно, поэтому подход throwing здесь, на мой взгляд, подходит. Это то, что делают и RxJava, и Reactor.

person Simon Baslé    schedule 15.03.2018