Проверенные и непроверенные исключения на сервисном уровне

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

UserService.get(userId);

Недавно я настаивал на том, чтобы этот API был изменен или дополнен новым API, который вместо этого генерирует исключения. Последовали дебаты о проверенных и непроверенных исключениях.

Принимая к сведению разработчиков JPA/Hibernate и др., я предположил, что непроверенные исключения могут быть наиболее подходящими. Мой аргумент заключается в том, что нельзя разумно ожидать, что пользователи API восстановятся после этих исключений, и в 99% случаев мы можем в лучшем случае уведомить пользователя приложения о возникновении некоторой ошибки.

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

Почему разработчики таких проектов, как JPA/EJB и Hibernate, выбрали модель неконтролируемых исключений? Есть ли для этого очень хорошее обоснование? Какие плюсы/минусы. Должны ли разработчики, использующие эти фреймворки, по-прежнему обрабатывать исключения времени выполнения рядом с тем местом, где они возникают, с помощью чего-то вроде оболочек адаптера?

Я надеюсь, что ответы на эти вопросы помогут нам принять «правильное» решение относительно нашего собственного сервисного уровня.


person S73417H    schedule 06.07.2011    source источник


Ответы (5)


Хотя я согласен с мнением, что непроверенные исключения делают API более удобным, я не считаю это самым важным преимуществом. Вместо этого это:

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

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

  • Проглотить. Перехватить исключение и полностью его проигнорировать. Продолжайте, как будто ничего не произошло, хотя приложение сейчас находится в нестабильном состоянии.
  • Зарегистрируйте и проглотите. Перехватите исключение и зарегистрируйте его, думая, что теперь мы несем ответственность. Дальше продолжайте как ни в чем не бывало.
  • Тайное значение по умолчанию. Перехватите исключение и установите для некоторого поля значение по умолчанию, обычно не сообщая об этом пользователю. Например, если вы не можете загрузить роли некоторых пользователей, просто выберите роль с низким уровнем привилегий и назначьте ее. Пользователь недоумевает, что происходит.
  • Глупое/опасное загадочное значение по умолчанию. Перехватите исключение и установите для некоторого поля какое-то действительно плохое значение по умолчанию. Один пример, который я видел в реальной жизни: не могу загрузить роли пользователя, поэтому продолжайте и принимайте лучшее (т.е. дайте им роль с высокими привилегиями, чтобы никому не причинять неудобств).
  • Неправильный отчет. Разработчик не знает, что означает исключение, и поэтому просто выдвигает собственную идею. IOException становится «Не удается подключиться к серверу», даже если установление соединения не имеет отношения к проблеме.
  • Маскируйте с помощью широкого перехвата и искажайте отчет. Попробуйте очистить код, перехватив Exception или (тьфу) Throwable вместо двух проверенных исключений, которые на самом деле выдает метод. Код обработки исключений не пытается отличить (скажем) проблемы с доступностью ресурсов (например, некоторые IOException) от явных ошибок кода (например, NullPointerException). Действительно, он часто произвольно выбирает одно из исключений и ошибочно сообщает о каждом исключении как об этом типе.
  • Маскировка с помощью огромной попытки и неверное сообщение. Вариант предыдущей стратегии заключается в том, чтобы поместить целую кучу вызовов, объявляющих исключения, в область действия одного большого блока попытки, а затем перехватить либо Exception, либо Throwable, потому что нет ничего другого, что обрабатывало бы все выбрасываемые исключения.
  • Повторное создание несоответствующей абстракции. Повторное создание исключения, даже если оно не соответствует абстракции (например, повторное создание исключения, связанного с ресурсом, из интерфейса службы, который должен скрывать ресурс).
  • Повторное создание без переноса. Повторное создание исключения (либо непроверенного, либо проверенного в соответствии с абстракцией), но просто отбрасывание вложенного исключения, которое даст любому возможность выяснить, что происходит.
  • Драматический ответ. Реагируйте на нефатальное исключение, выйдя из JVM. (Благодаря этой записи в блоге для этого.)

По моему опыту, гораздо чаще можно увидеть описанные выше подходы, чем правильный ответ. Многие разработчики — даже «старшие» — считают, что исключения должны подавляться любой ценой, даже если это означает запуск приложения в нестабильном состоянии. Это опасно неправильно.

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

person Community    schedule 06.07.2011

Я могу ошибаться, но это может быть связано с тем, как EJB-контейнер обрабатывает исключения. Из Рекомендации по обработке исключений EJB:

Чтобы использовать внутреннюю уборку контейнера EJB, вам нужно, чтобы проверенные исключения выбрасывались как непроверенные исключения.

person Denis Tulskiy    schedule 06.07.2011

Я вижу проверенное/непроверенное исключение в следующих ракурсах (взяв подсказку из стандартного JDK)

  1. Используйте исключение Checked, если ваш код/библиотека зависит от какого-либо ресурса, который является внешним по отношению к JVM (например, .file/socket/remote api и т. д., т.е. JVM не имеет над ним контроля). Ваш код ДОЛЖЕН обрабатывать все возможные условия ошибки, которые могут возникнуть в результате. Это делается для того, чтобы убедиться, что ваша программа надежна и корректно завершает работу в случае, если с этим ресурсом что-то не так.

  2. Непроверенные исключения — это серьезные ошибки ИЛИ настоящие ОШИБКИ в вашем коде, которые обычно не следует обрабатывать ПОСЛЕ того, как они произошли. Вы можете исправить свой код, чтобы эта ошибка не отображалась в первую очередь. (Уравнение НЛП, приведение классов, деление на ноль и т. д.)

person Santosh    schedule 06.07.2011

Как правило, рекомендуется генерировать RuntimeException, когда ваш код на более высоком уровне не может его обработать и ничего не может сделать с исключением.

В вашем случае ваш API вернет некоторый результат, если результат будет успешным, и должен вернуть нуль, если результаты не найдены, а затем выдать исключение, если что-то пошло не так при выполнении операции.

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

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

Например, у меня есть код, который выглядит так:

Вычтите платеж, а затем отправьте детали доставки. Если (отправка сведений о доставке не удалась), верните платеж, иначе отправьте письмо об успешном завершении.

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

Спасибо, Сэтвик

person Sathwick    schedule 06.07.2011

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

    try
    {
        File file = new File("file");
        FileReader fileReader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String temp = "";
        StringBuilder result = new StringBuilder("");
        while ((temp = bufferedReader.readLine()) != null)
        {
            result.append(temp);
        }
        System.out.println(result);
    }
    catch (FileNotFoundException e)
    {
        e.printStackTrace();
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }

Мы обрабатываем два типа исключений, но если при обработке этих исключений не может произойти никакого действия, зачем их перехватывать? Ответ заключается не в том, чтобы выбрасывать проверенные исключения, поскольку в конечном итоге какой-то класс вверх по иерархии вызовов должен будет его обработать (или он будет передан в JVM). Если разработчик действительно заинтересован в их обработке, поймайте конкретный класс RuntimeException, который решает проблему.

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

person Deepak Bala    schedule 06.07.2011