Рекомендации по обработке исключений RuntimeException

Предполагается, что RuntimeExceptions указывает на ошибку программирования, и я хочу, чтобы мое приложение вылетало, когда что-то внутри моих наблюдаемых выдает RuntimeException.

Как лучше всего это сделать? Вот сейчас обдумываю это решение (это Kotlin, но надеюсь понятно)

fun <T> Observable<T>.subscribeCrashOnRuntimeException(onNext: (T) -> Unit, onError: (Throwable) -> Unit) {
  this.subscribe({
    onNext(it)
  }, { e ->
    if (e is RuntimeException) {
      throw e
    } else {
      onError(e)
    }
  })
}

fun usageExample() {
  val observable = Observable.just(1)
  observable.subscribeCrashOnRuntimeExceptions(
    { next -> Log.d("TAG", "next: $next") },
    { e -> Log.d("TAG", "error: $e") }
  )
}

Но у меня есть сомнения по этому поводу. Например, с помощью этого решения трудно время от времени «поймать» определенные исключения RuntimeException. Возможно, есть известный способ справиться с ситуацией, которую я просто не знаю, как гуглить?


person Dmitry Ryadnenko    schedule 19.12.2016    source источник
comment
Прочтите о onErrorResumeNext, где вы можете решить, что делать с ошибкой.   -  person akarnokd    schedule 19.12.2016
comment
RuntimeExceptions are supposed to indicate programming error - не обязательно. Является ли отсутствие файла ошибкой программирования? Разорвано сетевое соединение? В большинстве случаев вы явно не хотите сбоя, а смягчите или повторите попытку.   -  person Tassos Bassoukos    schedule 19.12.2016
comment
Отсутствующий файл @TassosBassoukos не является RuntimeExceptions, то же самое верно и для разорванного сетевого соединения. Похоже, вы не понимаете, что такое RuntimeExceptions. Похоже, вы думаете, что это просто синоним исключения.   -  person Dmitry Ryadnenko    schedule 19.12.2016


Ответы (1)


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

Реактивные способы обработки ошибок:

  1. через операторов onErrorResumeNext или onErrorReturn; они позволяют проверить ошибку и, возможно, исправить ее
  2. через retry* семейство операторов; они позволяют проверить ошибку и, возможно, исправить ее с помощью повторной подписки (например, повторить сетевой вызов)
  3. через onError обратный звонок ваших подписчиков; кстати, если вы не укажете такой обратный вызов, ошибка будет выдана повторно в обычном стиле Java, поэтому ваша программа упадет

Связанная тема: Как обрабатывать различные типы ошибок в Retrofit Rx onError без уродливого экземпляра

Также обратите внимание на недостатки создания исключений обычным способом Java:

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

Пример кода

Observable.fromCallable(() -> {
    ...
    if (ok) return "Success!";
    else throw new RuntimeException("Failure at source");
})
.map(s -> {
    ... processing is bypassed in case of an error
})
.map(s -> {
    ...
    if (...) return s.upperCase();
    else throw new RuntimeException("Failure during processing");
})
.onErrorReturn(e -> {
    if (e.getMessage().contains("processing"))
        return "Recovered";
    throw Exceptions.propagate(e); // let it continue as an error
})
.subscribe(s -> println("got result: " + s),
           e -> println("got error: " + e);

Все исключения перехватываются RxJava и передаются по заданному маршруту.

Операторы onError* действуют как промежуточные catch блоки.

Обратный вызов onError подписчика действует как блок catch верхнего уровня.

Дополнительные ссылки по теме:

person Yaroslav Stavnichiy    schedule 12.01.2017
comment
Скажем, у меня есть некоторые наблюдаемые, которые должны возвращать либо данные, либо ошибку. Например, наблюдаемый, который выполняет http-запрос. Оба эти возвращаемых типа допустимы, и подписчик должен иметь возможность реагировать на них оба. Итак, насколько я понимаю, официальный способ RxJava справиться с этой ситуацией - вернуть либо объект данных, либо объект ошибки. И не предоставляйте обратный вызов onError(), поэтому приложение вылетит при любой неожиданной ошибке. Я прав? Такое ощущение, что этот подход приведет к тонне шаблонов, но, может быть, я ошибаюсь, думаю, стоит попробовать. - person Dmitry Ryadnenko; 12.01.2017
comment
@DmitryRyadnenko Observable, который выполняет http-запрос, должен либо вернуть ответ, либо выбросить исключение, которое будет перехвачено RxJava и направлено по линии как ошибка, что приведет к onError обратный звонок вашего абонента. - person Yaroslav Stavnichiy; 12.01.2017
comment
Вот как я сейчас поступаю, и в итоге я получил один и тот же шаблон в каждом методе onError. Подобно gist.github.com/rongi/3955d4946231c195d04adf110928a2ed, это загрязнило мой в остальном простой и чистый код подписки, поэтому я пришел с решением, описанным в вопросе. Это сделало мой код подписчика снова чистым. У вас была такая же проблема? Вы видите в этом проблему? Как вы решаете эту проблему? - person Dmitry Ryadnenko; 12.01.2017
comment
@DmitryRyadnenko Если обработчик ошибок одинаков для нескольких подписчиков, вы можете извлечь его в общую функцию или класс. Или просто опустите обработчик ошибок, добавьте catch на верхний уровень вашего приложения. - person Yaroslav Stavnichiy; 12.01.2017