Реактивный дизайн: ошибка выброса или публикации

В следующем примере кода есть запах (Rx) Swift, но вопрос является общим для любого языка с реактивными функциями и возможностью бросания.

Рассмотрим функцию, которая возвращает наблюдаемую последовательность, но выполняет некоторую проверку работоспособности перед созданием последовательности. Ошибка проверки означает, что последовательность не может производить значения.

func yieldFoos() -> Observable<Foo> {
  guard isValid(internalFoo) else {
    // throw or return one shot observable?
  }
  return createValidObservable(from: internalFoo)
}

В случае сбоя проверки достоверности состояния должна ли функция выдавать или возвращать один наблюдаемый выстрел, который когда-либо приведет только к ошибке? Мой программист обнаружил следующие плюсы и минусы:

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

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

У кого-нибудь есть твердое мнение, за которым стоит последовать? Или другое упущенное из виду элегантное решение?


person Pavel Zdenek    schedule 23.05.2017    source источник


Ответы (2)


Мне интересно, как вы считаете, что Observable не может выдавать ошибку. Это часть его работы.

Если подумать, ваша функция createValidObservable(from:) может выдать ошибку, несмотря на то, что ей передан действительный internalFoo, поэтому код, вызывающий yieldFoos(), должен быть подготовлен к обработке выданной ошибки в любом случае. С таким же успехом вы можете собрать весь свой код обработки ошибок вместе. Я бы пошел дальше и сделал бы вашу функцию create способной обрабатывать недопустимые foos, выдав ошибку и покончив с этой функцией yieldFoos.

Теперь, если вы хотите, чтобы yieldFoos() возвращал Driver, а не наблюдаемое, тогда вы должны обработать ошибку с помощью генерации или предварительного условия (потому что драйверы не выдают ошибок.)

func yieldFoos() -> Observable<Foo> {
    guard isValid(internalFoo) else {
        return Observable.error(myError)
    }
    return createValidObservable(from: internalFoo)
}

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

person Daniel T.    schedule 24.05.2017
comment
Нет ничего плохого в том, что Observable выдает ошибку. Я просто чувствую, что такая ошибка должна быть наблюдаемой (невозможность продолжить последовательность), а не создавать наблюдаемую в первую очередь. Сбой последовательности - это нормально, сбой создания - исключительная несогласованность, поэтому бросок казался уместным. Но существование оператора single shot Observable.error намекает на то, что я, возможно, просто слишком много думаю. - person Pavel Zdenek; 25.05.2017
comment
Если ваша функция создания получила недопустимый internalFoo, она не сможет продолжить последовательность ... :-) Это может помочь думать об ошибке Observable как об их версии throw или думать о ней как о throw для асинхронных событий. Он работает как бросок в том смысле, что он распространяется по трубе, пока не будет обработан ... - person Daniel T.; 25.05.2017
comment
так бы вы сказали - в реактивном коде в целом меньше вариантов использования для бросания? Я думаю, что одна из ключевых концепций реагирования заключается в том, что все может постепенно становиться асинхронным и даже ленивым, но вашему коду преобразования это не должно волновать. В то время как броски по своей сути синхронны ... - person Pavel Zdenek; 25.05.2017
comment
Нет, это не совсем так ... Я говорю, что событие onError Rx аналогично throw. Например, если вы вставите внутрь flatMapLatest или любой другой оператор Rx, он будет автоматически преобразован в событие onError. Исключения и события ошибок - это два разных способа представления одной и той же абстракции. Да, выброс выполняется синхронно, но также и события ошибок, поскольку они немедленно завершают наблюдаемую цепочку. - person Daniel T.; 25.05.2017
comment
@PavelZdenek Я вижу различие, которое вы разъяснили относительно сбоя создания и сбоя последовательности. Как вы относитесь к их различению, выдавая ошибку создания Observable и выдавая ошибку (соответственно)? То есть yieldFoos() throws -> Observable<Foo>, в котором, если не удается создать наблюдаемый, выдается ошибка. Однако, если в наблюдаемой последовательности есть ошибки, она завершается событием onError. Таким образом, создание ошибки наблюдаемого можно отличить от ошибок последовательности. - person dsapalo; 26.05.2017
comment
Это означает, что обработка ошибки для создания наблюдаемого будет обрабатываться в пределах локальной области действия закрытия (т.е. flatMap), которое вызывает yieldFoos(), потому что это потребует, чтобы вы обработали его с помощью синтаксиса do-try. С другой стороны, если вы придерживаетесь выдачи одной ошибки, чтобы остановить всю последовательность, у вас будет больший контроль или больше параметров, в которых родительский наблюдаемый может обрабатывать эту ошибку с помощью оператора catchError (лучшая инверсия зависимостей) по сравнению с локальной областью. для стандартного синтаксиса do-try. - person dsapalo; 26.05.2017

Ваша функция должна быть примерно такой:

func yieldFoos() -> Observable<Foo> {
    Observable.create { observer in 

        guard isValid(internalFoo) else {
            observer.onError(yourError)
        }

        let subscription =  
            createValidObservable(from: internalFoo)
                .subscribe(onNext: { foo in
                    observer.onNext(foo)
                    observer.onCompleted()
                })
        return Disposables.create {
            // your dispose
            subscription.dispose()
        }
    }

}

А потом, когда вы это вызовете:

yieldFoos()
    .subscribe(
     onNext: { foo in
         // your code with foo
     },
     onError: { error in
        // manage errors
     })
    .addDisposableTo(disposeBag)
person xandrefreire    schedule 23.05.2017
comment
Я знаю, как создавать и использовать наблюдаемые, спасибо. Я спрашиваю, следует ли исключать исключительные отказы или упорядочивать их. Кажется, вы предпочитаете последовательности, но без рассуждений. - person Pavel Zdenek; 25.05.2017