Дело против проверенных исключений - PullRequest
418 голосов
/ 05 марта 2009

В течение ряда лет я не мог получить достойный ответ на следующий вопрос: почему некоторые разработчики так против проверенных исключений? У меня было много разговоров, читал что-то в блогах, читал то, что говорил Брюс Экель (первый человек, которого я видел, выступал против них).

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

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

В целом (из того, как был разработан Java),

  • Ошибка для вещей, которые никогда не должны быть пойманы (у ВМ аллергия на арахис, и кто-то уронил на нее банку с арахисом)
  • RuntimeException для вещей, которые программист сделал неправильно (программист ушел с конца массива)
  • Исключение (кроме RuntimeException) относится к вещам, которые находятся вне контроля программиста (диск заполняется при записи в файловую систему, достигнут предел дескриптора файла для процесса, и вы больше не можете открывать файлы)
  • Throwable - это просто родитель всех типов исключений.

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

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

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

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

У меня нет проблем с переносом Main с catch (Exception) (или, в некоторых случаях, catch (Throwable), чтобы программа могла корректно завершиться - но я всегда ловлю конкретные исключения, которые мне нужны. Это позволяет мне, по крайней мере, отобразить соответствующее сообщение об ошибке.

Вопрос, на который люди никогда не отвечают, таков:

Если вы выбросите RuntimeException подклассы вместо исключения подклассы, то как вы знаете, что ты должен ловить?

Если ответом является catch Exception, то вы также имеете дело с ошибками программиста так же, как системные исключения. Это кажется мне неправильным.

Если вы перехватываете Throwable, то вы обрабатываете системные исключения и ошибки ВМ (и тому подобное) одинаково. Это кажется мне неправильным.

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

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

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

Редактировать: я не ищу совета о том, когда использовать какую-либо модель, я ищу , почему люди выходят из RuntimeException, потому что им не нравится выходить из Exception и / или почему они ловят исключение, а затем перебрасывать RuntimeException вместо добавления бросков к их методу. Я хочу понять мотивацию неприязни к проверенным исключениям.

Ответы [ 32 ]

14 голосов
/ 05 марта 2009

За последние три года я работал с несколькими разработчиками в относительно сложных приложениях. У нас есть кодовая база, которая довольно часто использует Checked Exceptions с надлежащей обработкой ошибок, а другая - нет.

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

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

Все это, конечно, вопрос предпочтений и навыков разработчика. Оба способа программирования и обработки ошибок могут быть одинаково эффективными (или неэффективными), поэтому я бы не сказал, что существует The One Way.

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

13 голосов
/ 12 мая 2013

Категории исключений

Говоря об исключениях, я всегда ссылаюсь на статью Эрика Липперта Vexing исключений в блоге. Он помещает исключения в следующие категории:

  • Фатальный - Эти исключения не являются вашей ошибкой : вы не можете предотвратить это, и вы не можете разумно справиться с ними. Например, OutOfMemoryError или ThreadAbortException.
  • Boneheaded - Эти исключения являются вашей ошибкой : вы должны были предотвратить их, и они представляют собой ошибки в вашем коде. Например, ArrayIndexOutOfBoundsException, NullPointerException или любой IllegalArgumentException.
  • Vexing - Эти исключения не исключительны , не ваша вина, вы не можете предотвратить их, но вам придется иметь дело с ними. Они часто являются результатом неудачного проектного решения, например, бросая NumberFormatException из Integer.parseInt вместо предоставления Integer.tryParseInt метода, который возвращает логическое значение false при сбое синтаксического анализа.
  • Exogenous - Эти исключения обычно являются исключительными , не по вашей вине, вы не можете (разумно) предотвратить их, но вы должны их обработать . Например, FileNotFoundException.

Пользователь API:

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

Проверенные исключения

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

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

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

Проблемы с API

Но любой API, который проверял неприятные и фатальные исключения (например, JCL), создаст ненужную нагрузку для пользователей API. Такие исключения должны обрабатываться , но либо исключение настолько распространено, что оно не должно быть исключением, либо ничего не может быть сделано при его обработке. И это заставляет разработчиков Java ненавидеть проверенные исключения.

Кроме того, многие API не имеют надлежащей иерархии классов исключений, в результате чего все виды неэкзогенных причин исключений представляются одним проверенным классом исключений (например, IOException). И это также заставляет разработчиков Java ненавидеть проверенные исключения.

Заключение

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

Так что не ненавидьте Java и его проверенные исключения. Вместо этого ненавидите API, которые чрезмерно используют проверенные исключения.

6 голосов
/ 07 августа 2009

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

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

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

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

Многие утверждают, что неспособность загрузить файл была почти всегда концом света для процесса, и он должен умереть ужасной и мучительной смертью. Так что да ... конечно ... хорошо ... вы создаете API для чего-то, и он загружает файл в какой-то момент ... Я, как пользователь указанного API, могу только отвечать ... "Кто, черт возьми, вы решаете, когда мой программа должна вылететь! Конечно, учитывая выбор, где исключения поглощаются и не оставляют следа, или исключение EletroFlabbingChunkFluxManifoldChuggingException с трассировкой стека глубже, чем траншея Марианны, я бы взял последний без малейших колебаний, но означает ли это, что это желательный способ справиться с исключением ? Разве мы не можем быть где-то посередине, где исключение будет переделываться и переноситься каждый раз, когда оно переходит на новый уровень абстракции, так что оно действительно что-то значит?

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

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

6 голосов
/ 05 марта 2009

Действительно, проверенные исключения, с одной стороны, повышают надежность и корректность вашей программы (вы вынуждены делать правильные объявления ваших интерфейсов - исключения, которые выдает метод, в основном являются специальным типом возврата). С другой стороны, вы сталкиваетесь с проблемой, заключающейся в том, что, поскольку исключения «всплывают», очень часто вам необходимо изменить целый ряд методов (всех вызывающих и вызывающих абонентов и т. Д.) При изменении одного исключения. метод throws.

Проверенные исключения в Java не решают последнюю проблему; C # и VB.NET выбрасывают ребенка с водой.

Хороший подход, который идет по средней дороге, описан в этой статье OOPSLA 2005 (или связанном техническом отчете .)

Короче говоря, он позволяет вам сказать: method g(x) throws like f(x), что означает, что g выбрасывает все исключения f бросков. Вуаля, проверил исключения без проблемы каскадных изменений.

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

5 голосов
/ 29 октября 2011

проблема

Наихудшая проблема, с которой я сталкиваюсь в механизме обработки исключений, заключается в том, что вводит дублирование кода в большом масштабе ! Давайте будем честными: в большинстве проектов в 95% случаев все, что действительно нужно разработчикам, - это как-то сообщить об этом пользователю (а в некоторых случаях и команде разработчиков, например, отправив электронное письмо). почта с трассировкой стека). Поэтому, как правило, одна и та же строка / блок кода используется в каждом месте, где обрабатывается исключение.

Предположим, что мы делаем простую регистрацию в каждом блоке catch для некоторого типа проверяемого исключения:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

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

Подожди ... мы действительно собираемся отредактировать все эти несколько сотен мест в коде ?! Вы поняли мою точку зрения: -).

Решение

Что мы сделали, чтобы решить эту проблему, так это ввести концепцию обработчиков исключений (на которую я буду далее ссылаться как EH) для централизации обработки исключений. Каждому классу, который должен обрабатывать исключения, экземпляр обработчика исключений внедряется нашей структурой Dependency Injection . Таким образом, типичная схема обработки исключений теперь выглядит так:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

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

Конечно, для более сложных случаев мы можем реализовать несколько подклассов EH и использовать функции, которые предоставляет нам наша структура DI. Изменяя нашу конфигурацию структуры DI, мы можем легко переключать реализацию EH глобально или предоставлять конкретные реализации EH для классов с особыми потребностями обработки исключений (например, с помощью аннотации Guice @Named).

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

Последняя вещь

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

5 голосов
/ 04 июля 2012

Чтобы попытаться ответить только на оставшийся без ответа вопрос:

Если вы бросаете подклассы RuntimeException вместо подклассов Exception, то как вы узнаете, что вы должны ловить?

Вопрос содержит в себе неявные рассуждения ИМХО. Тот факт, что API сообщает вам, что он выдает, не означает, что вы будете работать с ним одинаково во всех случаях. Другими словами, исключения, которые вам нужно перехватить, различаются в зависимости от контекста, в котором вы используете компонент, выдававший исключение.

Например:

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

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

5 голосов
/ 05 марта 2009

Андерс говорит о подводных камнях проверенных исключений и о том, почему он исключил их из C # в эпизоде ​​97 радио Software Engineering.

4 голосов
/ 14 апреля 2016

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

По классу Exception можно себе представить? Что ж, нет, потому что Exception s являются исключениями, а также исключениями являются Exception s, за исключением тех исключений, которые не Exception s, потому что другие исключения на самом деле Error s, которые представляют собой другой вид исключения, своего рода исключительное исключение, которое никогда не должно происходить, кроме случаев, когда оно происходит, и которое вы никогда не должны ловить, кроме случаев, когда это необходимо. За исключением того, что это еще не все, потому что вы также можете определить другие исключения, которые не являются ни Exception с, ни Error с, а просто Throwable исключениями.

Какие из них являются "проверенными" исключениями? Throwable s являются проверенными исключениями, за исключением случаев, когда они также Error s, которые являются непроверенными исключениями, а затем есть Exception s, которые также являются Throwable s и являются основным типом проверяемого исключения, кроме есть и одно исключение: если они также RuntimeException с, потому что это другой тип непроверенного исключения.

Для чего RuntimeException с? Ну, как следует из названия, это исключения, как и все Exception с, и они происходят во время выполнения, как и все исключения на самом деле, за исключением того, что RuntimeException с являются исключительными по сравнению с другими Exception во время выполнения потому что они не должны происходить, за исключением случаев, когда вы совершаете какую-то глупую ошибку, хотя RuntimeException s никогда не бывает Error s, поэтому они предназначены для вещей, которые являются исключительно ошибочными, но на самом деле не являются Error s. За исключением RuntimeErrorException, который действительно равен RuntimeException для Error с. Но разве не все исключения должны представлять ошибочные обстоятельства? Да, все из них. За исключением ThreadDeath, исключительно исключительного исключения, поскольку документация объясняет, что это «нормальное явление» и именно поэтому они сделали его типом Error.

В любом случае, поскольку мы делим все исключения по середине на Error s (которые предназначены для исключительных исключений при выполнении, поэтому не проверяются) и Exception s (которые предназначены для менее исключительных ошибок выполнения, поэтому проверяются, кроме случаев, когда они нет), теперь нам нужно два разных вида каждого из нескольких исключений. Таким образом, нам нужно IllegalAccessError и IllegalAccessException, и InstantiationError и InstantiationException и NoSuchFieldError и NoSuchFieldException и NoSuchMethodError и NoSuchMethodException и ZipError и ZipException.

За исключением того, что даже когда проверяется исключение, всегда есть (довольно простые) способы обмануть компилятор и выбросить его без проверки. Если вы сделаете это, вы можете получить UndeclaredThrowableException, за исключением других случаев, когда его могут выкинуть как UnexpectedException или UnknownException (что не связано с UnknownError, что только для "серьезных исключений"), либо ExecutionException, либо InvocationTargetException или ExceptionInInitializerError.

О, и мы не должны забывать о шикарном новом Java 8 UncheckedIOException, который является RuntimeException исключением, разработанным, чтобы позволить вам выбросить концепцию проверки исключений из окна, упаковав флажок IOException исключения, вызванные ошибками ввода-вывода (которые не вызывают IOError исключений, хотя они тоже существуют), которые исключительно сложны в обработке, поэтому их нужно не проверять .

Спасибо, Java!

4 голосов
/ 07 ноября 2009

Моя запись на c2.com по-прежнему в основном не изменилась по сравнению с первоначальной формой: CheckedExceptionsAreIncompatibleWithVisitorPattern

В итоге:

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

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

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

Что касается основной проблемы:

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

4 голосов
/ 18 мая 2009

Как уже говорили, проверенные исключения не существуют в байт-коде Java. Они просто механизм компилятора, мало чем отличающийся от других проверок синтаксиса. Я вижу проверенные исключения во многом так же, как компилятор, жалующийся на избыточное условие: if(true) { a; } b;. Это полезно, но я мог сделать это специально, поэтому позвольте мне проигнорировать ваши предупреждения.

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

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

...