В Java, использует броски Exception вместо того, чтобы бросать несколько конкретных исключений?

При просмотре структуры Spring MVC я заметил, что, если я не понимаю, разработчики предпочитают исключать Exception вместо того, чтобы бросать несколько исключений.

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

Ответ 1

Что имеет смысл для библиотеки, такой как Spring MVC, которая должна быть достаточно открытой, чтобы соответствовать различным видам использования, не обязательно имеет смысл следовать вам при написании конкретного приложения. Это один из таких случаев.

Если вы имеете в виду классы, такие как интерфейс Controller, который как подпись метода, например

handleRequest(HttpServletRequest request, HttpServletResponse response) 
   throws Exception

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

Другими словами, DispatcherServlet не нуждается в том, чтобы знать, какой конкретный тип исключений может вызвать ваш Контроллер - он будет рассматривать любой из них как "ошибку". Вот почему подпись метода throws Exception.

Теперь авторы API могли заставить подпись использовать специальный тип исключения как SpringMvcException, но это могло бы только заставить вас обрабатывать любые проверенные типы исключений в вашем методе handleRequest и просто обернуть их, который представляет собой утомительный код шаблона работы. Итак, поскольку почти все с Spring предназначено для того, чтобы сделать его максимально простым и легким для интеграции, насколько это возможно, им проще указать, что метод интерфейса просто throws Exception.

Ответ 2

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

Обновление. Чтобы ответить на некоторые из возражений "что, если я переопределяю метод", я бы пошел немного дальше и скажу, что всякий раз, когда у вас есть пакет, который генерирует исключения (в отличие от исключения исключения из вызывающего), вы должны объявить класс исключения в этом пакете. Таким образом, если вы переопределяете мой "addToSchedule() throws ScheduleConflictException", вы вполне можете подклассифицировать ScheduleConflictException, чтобы сделать то, что вам нужно.

Ответ 3

Здесь проблема с бросанием определенных исключений... Предположим, кто-то расширяет ваш класс и хочет переопределить ваш метод. Предположим, что их новая реализация должна генерировать другой тип исключения. (Как бы вы когда-либо могли предсказать, какие исключения может потребоваться для переопределения метода?) Человек, записывающий метод переопределения, имеет только два варианта: 1) обрабатывать исключение сам (вероятно, плохой выбор) или 2) обернуть реальный исключение в одном из разрешенных типов исключений и повторного броска.

Но вариант 2 имеет две проблемы. Во-первых, когда вы выгружаете свои исключения из ваших файлов журналов, вы получите длинные уродливые цепи вложенных исключений. Что еще более важно, вы потеряете способность ловить определенные исключения. Например, предположим, что метод переопределения вызывает другой метод, который ведет переговоры с базой данных и генерирует исключение DeadlockException, если полученный SQL вызвал тупик. Переопределяющий метод должен поймать это исключение, обернуть его в один из разрешенных типов и повторно бросить. Это делает невозможным дальнейшее расширение кода, чтобы поймать и обнаружить исключение DeadlockException.

В конечном итоге ваш вопрос попадает в суть дебатов о проверенных и неконтролируемых исключениях. Вы можете Google и найти множество аргументов для обеих сторон дискуссии. Я думаю, что в конечном счете, если вы верите в проверенные исключения, вы должны быть очень ясны в отношении того, какие исключения вызывает метод. Если вам не нравятся проверенные исключения, вы должны объявить каждый метод для исключения Exception. Я попадаю в последний лагерь.

Кстати, для людей, которым не нравятся проверенные исключения, мне не нравится идея использования RuntimeException во всем мире. Проблема в том, что вам, скорее всего, потребуется включить стороннюю библиотеку, которая использует исключение, а не RuntimeException. Затем вашему коду придется поймать все Исключение из библиотеки и обернуть их в RuntimeException. Это создает беспорядок.

Итак, если я снова начинаю проект Java с нуля, я просто объявляю, что каждый метод бросает Exception.

Ответ 4

Нет.

Throwing Exception заставляет код вызывать исключение, которое они, вероятно, не захотят делать по всем причинам.

Ответ 5

IMHO, все обсуждения и рекомендации по дизайну говорят хорошие вещи о том, что должно быть сделано В ОБЩЕМ, и у них есть принцип, который их поддерживает. Но обычно эти рекомендации не применяются ко всем случаям.

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

Ответ 6

IMO - самая большая причина не бросать исключение, так это то, что он заставляет наших клиентов catch(Exception e), если они действительно заинтересованы в принятии каких-либо действий.

Проблема с catch(Exception e) заключается в том, что Exception при проверке также является суперклассом RuntimeException и поэтому заставляет клиента также улавливать все RuntimeExceptions.

К сожалению, нет метода catch (CheckedException e). Предполагая, что вашему бедному клиенту не нужно улавливать RuntimeExceptions, они должны выполнить проверку экземпляра и повторно бросить, если это исключение RuntimeException.

Ответ 7

По-моему, да, потому что это позволяет вам делать любое исключение.

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

Просто использование "throws Exception" позволяет любому методу, вызывающему ваш метод, надлежащим образом обрабатывать любые методы, которые вы указали, но всякий раз, когда вы добавляете новые исключения в свою функцию, он не нарушает эти другие методы. (Хотя вам, вероятно, следует обновить их для обработки нового исключения, но это не требуется.)

Ответ 8

Брайан Гетц затрагивает некоторые из этих проблем здесь. Также см. Эффективная Java 2-е издание от Джоша Блоха. Он посвящает целый раздел "Исключения". Кроме того, Род Джонсон, основатель Spring Framework, подробно описывает свои мысли о обработке Java Exception в J2EE Design and Development. Вы можете или не соглашаетесь с его исключительной обработкой философии, но, по крайней мере, может быть больше смысла, почему они приняли решения, которые они сделали.

alt text http://java.sun.com/docs/books/images/effective.jpg alt text

Ответ 9

Вам нужно различать общий код и более конкретный код.

Рассмотрим возвращаемые типы функций как аналогию. Если вы пишете класс типа "ArrayList", вы хотите, чтобы он был очень общим, поэтому параметры и возвращаемые значения часто являются "объектами". Но когда вы пишете код, более специфичный для вашего приложения, например функцию "getRetiredEmployees", было бы очень плохо вернуться к Object. Вы скорее захотите вернуть Employee [] или что-то в этом роде.

Таким образом, фреймворк вроде Spring будет ожидать генерических исключений, потому что он не имеет ни малейшего представления о том, какие исключения вы выбрали для своего конкретного приложения. Никоим образом авторы Spring не могли знать, что вы собираетесь выбросить исключение "EmployeeNotInSpecifiedDepartment" или что-то еще. Как они могли? Но если вы пишете функцию sendPensionChecks, и вы вызываете getRetiredEmployees, вы можете разумно ожидать определенных исключений, которые может вызвать эта функция, и что вы должны сделать для их обработки.

Клинт Миллер поднимает действительный момент о том, чтобы не знать, как класс может быть расширен. Я признаю, что это проблема с конкретными исключениями. Но оттуда, чтобы "просто сделать все, что является общим исключением", слишком легко сдаётся. Это похоже на то, что кто-то когда-нибудь может расширить нашу функцию getRetiredEmployees, чтобы в некоторых случаях вернуть EmployeeSpouse вместе с Employee's, поэтому мы должны просто отказаться и сделать возвращаемый тип Object. Если это код, который вы используете внутренне, и вы контролируете его, это не проблема: если вам нужно добавить новое исключение в функцию, добавьте его. Если это API, который вы публикуете в мире, то да, проблема намного сложнее. Я бы сказал, что общее решение состоит в том, чтобы попытаться разумно сообразить, какие исключения имеют смысл и включать их всех, даже если они не все в настоящее время реализованы. Если один из ваших клиентов делает что-то совершенно непредвиденное, ну, это проблема, для которой нет простого ответа.

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

Ответ 10

Я действительно удалил проверенные исключения из компилятора.

Это работает лучше, чем можно было бы предположить.

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

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

Ответ 11

Перечислите все исключения. Таким образом:
 * Он четко определяет, что может пойти не так.  * Пользователи вашего класса могут точно знать, какие исключения они должны обрабатывать.

Кроме того, переопределяющий метод должен делать ТОЛЬКО ТОЧНОЕ как переопределенное, но по-другому. Следовательно, как вы можете получить другое исключение, которое НЕ является подклассом уже заброшенного исключения? Вы не должны. Кроме того, вы НЕ ДОЛЖНЫ бросать новые исключения в подкласс, так как подкласс должен быть способен заменить его родительским классом, следовательно, если он передается как "p" на

public void method(Parent p);

Как бы "метод" знал, что он должен обрабатывать какое-то новое исключение? Это не нужно. И это означает неправильный дизайн (либо родительского класса, либо подкласса).

Ответ 12

В общем, "бросает исключение" редко бывает хорошей идеей.

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

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

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

Объявление заброшенных исключений - это, по сути, договор между авторами метода и использующими его клиентами, говорящий, что вы ожидаете, может пойти не так. Одеяло "бросает исключение" эквивалентно тому, что вы поднимаете руки и говорите, что клиенты должны быть готовы к НИЧЕГО, чтобы пойти не так, даже проблемы, которые должны были быть найдены и исправлены во время ранней отладки. Звонящие должны либо поймать эти исключения - и обычно будут просто есть их и продолжать, так как контракт настолько широк, что они понятия не имеют, откуда возникла эта проблема, или бросают их вверх, заставляя кого-то другого заниматься теми же проблемами.

Ответ 13

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

Возможная ошибка и решение:

при анализе ints - NumberFormatException. Мы могли бы иметь значение по умолчанию

В нижней строке, я считаю, что лучше соблюдать отдельные исключения.

Ответ 14

Исключение броска может также вызвать множество проблем с управлением транзакциями в J2EE. Использование исключения catch - это чистое зло!

Я не дам вам котят!

Ответ 15

Tricky.

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

При написании фреймворка или библиотеки, хотя это невозможно сказать -

абстрактный T func (...) бросает A, B, C;

потому что любой класс, который расширяет контейнер func (...), может также переопределить func.

Мне пришлось использовать более легкую спецификацию "throws Exception", но, как правило,

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