Является ли в Java использование throws Exception вместо бросания нескольких конкретных исключений хорошей практикой? - PullRequest
13 голосов
/ 12 июня 2009

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

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

Ответы [ 15 ]

18 голосов
/ 12 июня 2009

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

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

16 голосов
/ 12 июня 2009

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

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

handleRequest(HttpServletRequest request, HttpServletResponse response) 
   throws Exception

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

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

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

7 голосов
/ 12 июня 2009

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

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

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

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

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

3 голосов
/ 25 июня 2009

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

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

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

3 голосов
/ 12 июня 2009

номер

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

2 голосов
/ 12 июня 2009

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

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

1 голос
/ 11 апреля 2012

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

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

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

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

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

1 голос
/ 22 июня 2009

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

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

public void method(Parent p);

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

1 голос
/ 12 июня 2009

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

Это работает лучше, чем можно догадаться.

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

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

1 голос
/ 12 июня 2009

Необходимо различать общий код и более конкретный код.

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...