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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 1

Я думаю, что я прочитал то же интервью Брюса Эккеля, что и ты, и это всегда меня било. Фактически, аргумент был сделан собеседником (если это действительно сообщение, о котором вы говорите) Андерс Хейлсберг, гениальность MS за.NET и С#.

http://www.artima.com/intv/handcuffs.html

Хотя я поклонник Хейльсберга и его работа, этот аргумент всегда казался мне фиктивным. Это в основном сводится к:

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

Под "иначе представленным пользователю" я имею в виду, что если вы используете исключение во время выполнения, ленивый программист просто проигнорирует его (против того, чтобы поймать его с пустым блоком catch), и пользователь увидит его.

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

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

Но, в конце концов, я нахожу это фиктивным аргументом Хейльсберга и, возможно, пост-hoc, созданного для объяснения недостатка, а не хорошо продуманного решения.

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

Теперь программист API должен быть осторожным, чтобы не бросать проверенные исключения по всему месту, или они просто раздражают клиентского программиста. Очень ленивый клиент-программист прибегнет к улову (Exception) {} как предупреждает Хейлсберг, и вся польза будет потеряна, и ад наступит. Но в некоторых случаях просто нет замены для хорошего проверенного исключения.

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

В C идиома идет что-то вроде

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

где fopen указывает на отказ, возвращая 0 и C (глупо), вы можете обрабатывать 0 как логическое и... В принципе, вы изучаете эту идиому, и все в порядке. Но что, если вы ноб, и вы не изучили идиому. Тогда, конечно, вы начинаете с

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

и учиться трудно.

Обратите внимание, что здесь мы говорим только о строго типизированных языках: существует четкое представление о том, что API находится на строго типизированном языке: это превосходная функциональность (методы) для использования с четко определенным протоколом для каждого из них.

Этот четко определенный протокол обычно определяется сигнатурой метода. Здесь fopen требует, чтобы вы передавали ему строку (или char * в случае C). Если вы дадите ему что-то еще, вы получите ошибку времени компиляции. Вы не следовали протоколу - вы не используете API должным образом.

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

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

(На динамически типизированном языке, таком как Ruby, вы можете передать что угодно, скажем, float, как имя файла, и оно будет скомпилировано. Зачем беспокоить пользователя с проверенными исключениями, если вы даже не собираетесь управлять аргументами метода. аргументы, применяемые здесь, применяются только к статически типизированным языкам.)

Итак, что насчет проверенных исключений?

Ну вот один из Java API, который вы можете использовать для открытия файла.

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

Увидеть этот улов? Здесь подпись для этого метода API:

public FileInputStream(String name)
                throws FileNotFoundException

Обратите внимание, что FileNotFoundException является проверенным исключением.

Программист API говорит об этом вам: "Вы можете использовать этот конструктор для создания нового FileInputStream, но вы

а) должен передать имя файла в виде строки
b) должен принять возможность того, что файл не может быть найден во время выполнения "

И все это касается меня.

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

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

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

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

public RowData getRowData(int row) 

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

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(На самом деле я бы не назвал это "проверено").

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

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

С проверенным исключением в getRowData это, очевидно, случай, который приведет к ленивому программисту Хейльсберга, просто добавив пустые уловы. Когда это произойдет, недопустимые значения строк не будут очевидны даже для отладки тестировщика или клиента, но они приведут к ошибкам с детонацией, которые трудно определить источник. Ракеты Arianne взорвутся после запуска.

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

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

  1. Вне клиентского элемента управления или закрыто или открыто:

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

  2. Ubiquity:

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

    if (model.getRowData().getCell(0).isEmpty())
    

    и будет больно, чтобы каждый раз затягивать в try/catch.

  3. Информирование пользователя:

    Проверенные исключения следует использовать в тех случаях, когда вы можете представить полезное сообщение об ошибке, которое будет представлено конечному пользователю. Это "и что вы будете делать, когда это произойдет?" вопрос, который я поднял выше. Это также относится к пункту 1. Поскольку вы можете предсказать, что что-то вне вашей системы client-API может привести к тому, что файл не будет там, вы можете разумно сказать пользователю об этом:

    "Error: could not find the file 'goodluckfindingthisfile'"
    

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

    "Internal error occured: IllegalArgumentException in ...."
    

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

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

Извините, это не значит, что так долго и беспомощно. Позвольте мне закончить с двумя предложениями:

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

B: Клиентские программисты: привыкайте создавать завернутое исключение (google it) на ранней стадии разработки. JDK 1.4 и более поздние RuntimeException для этого конструктора в RuntimeException, но вы также можете легко создать свой собственный. Здесь конструктор:

public RuntimeException(Throwable cause)

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

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

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

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}

Ответ 2

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

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

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

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

Проблема заключается в том, что большое количество кода, в том числе большое количество стандартной библиотеки, неправильно использует исключения для реальных исключительных условий, которые вы вполне можете захотеть поймать на нескольких уровнях. Почему IOException не исключение RuntimeException? На любом другом языке я могу позволить исключение IO, и если я ничего не сделаю для этого, мое приложение остановится, и я получу удобную трассировку стека, чтобы посмотреть. Это лучшее, что может случиться.

Может быть, два метода из примера, который вы хотите уловить все IOExceptions из всего процесса записи в поток, прервать процесс и перейти в код сообщения об ошибках; в Java вы не можете этого сделать, не добавляя "throws IOException" на каждый уровень вызова, даже уровни, которые сами не выполняют IO. Такие методы не должны знать об обработке исключений; добавив исключения в свои подписи:

  • излишне увеличивает связь;
  • упрощает изменение сигнатур интерфейса;
  • делает код менее читаемым;
  • настолько раздражает, что общая реакция программиста состоит в том, чтобы победить систему, сделав что-то ужасное, как "бросает исключение", catch (Exception e) {} или обертывает все в RuntimeException (что усложняет отладку).

А потом много просто смешных библиотечных исключений вроде:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

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

Другим конкретным плохим эффектом является инверсия элемента управления, когда компонент A передает обратный вызов на общий компонент B. Компонент A хочет, чтобы исключение выбрало из своего обратного вызова обратно в место, где оно называлось компонентом B, но оно не может, потому что это изменило бы интерфейс обратного вызова, который был зафиксирован B. Может это сделать, только обертывая реальное исключение в RuntimeException, которое является еще большим шаблоном обработки исключений для записи.

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

Ответ 3

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

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

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

Я также потерял подсчет количества раз, когда я это видел:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

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

И тогда мы приводим в действие код, который использует исключения как форму управления потоком, например java.text.Format. Bzzzt. Неправильно. Пользователь, помещающий "abc" в поле чисел в форме, не является исключением.

Хорошо, я думаю, это было три причины.

Ответ 4

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

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

Возьмите хороший ol 'Java List интерфейс. У нас есть общие реализации в памяти, такие как ArrayList и LinkedList. У нас также есть скелетный класс AbstractList, который упрощает разработку новых типов списков. Для списка только для чтения нам нужно реализовать только два метода: size() и get(int index).

В этом примере класс WidgetList читает некоторые файлы фиксированного размера типа Widget (не показаны) из файла:

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

Выставляя Виджеты с помощью знакомого интерфейса List, вы можете извлекать элементы (list.get(123)) или перебирать список (for (Widget w : list) ...), не требуя знать о WidgetList. Этот список можно передать любым стандартным методам, использующим общие списки, или обернуть их в Collections.synchronizedList. Кодекс, который его использует, не нуждается ни в понимании, ни в заботе о том, сделаны ли "Виджеты" на месте, поступают из массива или читаются из файла, базы данных или по всей сети или из будущего подпространства. Он по-прежнему будет работать правильно, потому что интерфейс List правильно реализован.

Кроме того, это не так. Вышеупомянутый класс не компилируется, потому что методы доступа к файлам могут вызывать IOException, проверенное исключение, которое вы должны "уловить или указать". Вы не можете указать его как брошенный - компилятор не позволит вам, потому что это нарушит контракт интерфейса List. И нет никакого полезного способа, чтобы WidgetList сам мог обработать исключение (как я расскажу позже).

По-видимому, единственное, что нужно сделать, это catch и rethrow checked exceptions как некоторая исключенная исключение:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((Редактирование: Java 8 добавила класс UncheckedIOException именно для этого случая: для ловли и реорганизации IOException по полиморфному методу границы доказывают мою точку зрения!))

Таким образом, проверенные исключения просто не работают в таких случаях. Вы не можете их бросить. То же самое для умного Map, поддерживаемого базой данных, или реализации java.util.Random, подключенного к источнику квантовой энтропии через COM-порт. Как только вы попытаетесь сделать что-нибудь новое с реализацией полиморфного интерфейса, концепция проверенных исключений не удалась. Но проверенные исключения настолько коварны, что они все равно не оставят вас в покое, потому что вам все равно придется поймать и реконструировать любые методы нижнего уровня, загромождать код и загромождать трассировку стека.

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

Собственно, вы можете бросить необъявленные проверенные исключения, если вы прибегаете к хакам. JVM во время выполнения не заботится о проверенных правилах исключения, поэтому нам нужно обмануть только компилятор. Самый простой способ сделать это - злоупотреблять дженериками. Это мой метод для него (имя класса показано потому, что (до Java 8) оно требовалось в синтаксисе вызова для универсального метода):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

Ура! Используя это, мы можем выставить проверенное исключение на любую глубину стека, не объявляя его, не обернув его в RuntimeException и не загромождая трассировку стека! Повторное использование примера "WidgetList":

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

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

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

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

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


Когда отмеченные исключения соответствуют полиморфизму, противоположный случай также является проблемой: когда метод специфицирован как потенциально бросающий проверенное исключение, но переопределенная реализация не делает этого. Например, абстрактный класс OutputStreamВ методах write указывается throws IOException. ByteArrayOutputStream - это подкласс, который записывает в массив в памяти вместо истинного источника ввода-вывода. Его переопределенные методы write не могут вызывать IOException s, поэтому они не имеют предложения throws, и вы можете вызывать их, не беспокоясь о требовании catch-or-specific.

Кроме того, не всегда. Предположим, что Widget имеет способ сохранения его в потоке:

public void writeTo(OutputStream out) throws IOException;

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

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

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

Но подождите, проверенные исключения на самом деле так раздражают, что они даже не позволят вам сделать обратное! Представьте, что вы в данный момент поймали любой IOException, вызванный write вызовами в OutputStream, но вы хотите изменить объявленный тип переменной на ByteArrayOutputStream, компилятор будет бить вас, пытаясь поймать проверенное исключение, которое говорит не может быть брошен.

Это правило вызывает некоторые абсурдные проблемы. Например, один из трех методов write OutputStream не перекрывается ByteArrayOutputStream. В частности, write(byte[] data) - метод удобства, который записывает полный массив, вызывая write(byte[] data, int offset, int length) со смещением 0 и длиной массива. ByteArrayOutputStream переопределяет метод с тремя аргументами, но наследует метод удобства с одним аргументом as-is. Унаследованный метод выполняет именно правильную вещь, но включает нежелательное предложение throws. Возможно, это был надзор в дизайне ByteArrayOutputStream, но они никогда не смогут его исправить, потому что он нарушит совместимость источника с любым кодом, который поймает исключение - исключение, которое никогда, никогда и никогда не будет выброшено!

Это правило раздражает во время редактирования и отладки. Например, иногда я временно задерживаю вызов метода, и если бы он мог вызвать исключение, то компилятор теперь будет жаловаться на существование локальных блоков try и catch. Поэтому я должен также прокомментировать их, и теперь, когда редактирование кода внутри, IDE будет отступать до неправильного уровня, потому что { и } закомментированы. Г! Это небольшая жалоба, но, похоже, единственное, что проверяет исключения, когда-либо возникающие, вызывает проблемы.


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

Вместо этого мы получаем следующую идиому, которая распространена как способ закрыть компилятор:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

В графическом интерфейсе или автоматической программе печатное сообщение не будет видно. Хуже того, после исключения он сбрасывается с остальной частью кода. Является ли исключение на самом деле ошибкой? Затем не печатайте. В противном случае что-то еще взорвется через мгновение, и к тому времени исходный объект исключения исчезнет. Эта идиома не лучше BASIC On Error Resume Next или PHP error_reporting(0);.

Вызов какого-то класса журнала не намного лучше:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

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

Но подождите! Существует простой и универсальный способ поиска обработчика приложения. Он выше стека вызовов (или он устанавливается как Thread обработчик неперехваченных исключений). Поэтому в большинстве случаев все, что вам нужно сделать, это выбросить исключение выше стека. Например, throw e;. Проверенные исключения просто мешают.

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

Ответ 5

Ну, это не об отображении stacktrace или бесшумном сбое. Это о том, как можно передавать ошибки между слоями.

Проблема с проверенными исключениями заключается в том, что они побуждают людей усвоить важные детали (а именно класс исключений). Если вы решите не проглатывать эту деталь, вам нужно продолжать добавлять объявления бросков по всему вашему приложению. Это означает, 1) что новый тип исключения будет влиять на множество сигнатур функций, и 2) вы можете пропустить конкретный экземпляр исключения, которое вы на самом деле хотите, чтобы поймать (скажем, вы открываете дополнительный файл для функции, которая записывает данные в файл. Дополнительный файл является необязательным, поэтому вы можете игнорировать его ошибки, но поскольку подпись throws IOException, легко упустить это).

Я действительно занимаюсь этой ситуацией сейчас в приложении. Мы переупаковывали почти исключения как AppSpecificException. Это сделало подписи действительно чистыми, и нам не пришлось беспокоиться о взрывах throws в подписях.

Конечно, теперь нам нужно специализировать обработку ошибок на более высоких уровнях, реализуя логику повтора и т.д. Однако все это AppSpecificException, поэтому мы не можем сказать: "Если исключение IOException выбрано, повторите попытку" или "Если ClassNotFound выбрано, полностью прекратите". У нас нет надежного способа получить реальное исключение, потому что вещи переупаковываются снова и снова, когда они проходят между нашим кодом и сторонним кодом.

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

Я нашел снова и снова, и в течение всего проекта, о котором я упоминал, обработка исключений относится к трем категориям:

  • Поймать и обработать конкретное исключение. Например, для реализации логики повтора.
  • Ловить и реконструировать другие исключения. Все, что происходит здесь, обычно регистрируется, и обычно это банальное сообщение типа "Невозможно открыть $filename". Это ошибки, о которых вы ничего не можете сделать; только более высокий уровень знает достаточно, чтобы справиться с этим.
  • Поймать все и отобразить сообщение об ошибке. Обычно это находится в самом корне диспетчера, и все, что он делает, позволяет ему сообщать об ошибке вызывающей стороне через механизм без исключения (всплывающий диалог, маршалинг объекта ошибки RPC и т.д.).

Ответ 6

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

Предложение throws, по крайней мере, так, как оно реализовано на Java, не обязательно заставляет вас обрабатывать исключения, но если вы их не обрабатываете, это заставляет вас точно определить, какие исключения могут пройти. Это требует, чтобы вы либо ломали объявленные исключения, либо помещали их в свой собственный бросок. Чтобы обойти это требование, люди делают нелепые вещи. Например, они украшают каждый метод, "бросает исключение". Это просто полностью побеждает эту особенность, и вы просто заставили программиста написать более грязный мусор. Это никому не помогает.

Ответ 7

SNR

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

Обновить пользовательский интерфейс из неинтерфейса в Java:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Обновить пользовательский интерфейс из неинтерфейса в С#:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

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

Разрыв в тюрьме

Чтобы реализовать даже самые основные реализации, такие как интерфейс Java-списка, проверенные исключения как инструмент для проектирования по контракту падают. Рассмотрим список, который поддерживается базой данных или файловой системой или любой другой реализацией, которая выдает проверенное исключение. Единственная возможная реализация заключается в том, чтобы поймать проверенное исключение и переустановить его как исключенное исключение:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

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

Заключение

  • Захват исключений отличается от их обработки.
  • Проверенные исключения добавляют шум к коду.
  • Обработка исключений хорошо работает на С# без них.

Я писал об этом ранее.

Ответ 8

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

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

И причина в том, что, если это так, я должен это исправить, и я должен исправить это сразу. Я хочу знать сразу, что есть проблема.

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

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

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

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

Ответ 9

Короче:

Исключения - это вопрос проектирования API. - Не больше, не меньше.

Аргумент для отмеченных исключений:

Чтобы понять, почему проверенные исключения могут быть не очень удачными, позвольте задать вопрос и спросите: когда или почему проверяются исключения, привлекательные, то есть почему вы хотите, чтобы компилятор выполнял объявление об исключениях?

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

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

В действительности, хотя слишком часто пакет com.acme генерирует только AcmeException, а не определенные подклассы. Затем вызывающим абонентам необходимо обрабатывать, объявлять или повторно сигнализировать AcmeExceptions, но все же не может быть уверен, произошло ли событие AcmeFileNotFoundError или AcmePermissionDeniedError.

Итак, если вас интересует только AcmeFileNotFoundError, решение должно подать запрос функции с помощью программистов ACME и сообщить им, чтобы реализовать, объявить и документировать этот подкласс AcmeException.

Так зачем беспокоиться?

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

В результате языки без проверенных исключений обычно не намного хуже. У программистов может возникнуть соблазн бросить неспецифические экземпляры общего класса Error, а не AcmeException, но если они вообще не заботятся об их качестве API, они научатся вводить AcmeFileNotFoundError в конце концов.

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

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

Также стоит отметить, что Java VM не имеет проверенных исключений - только компилятор Java проверяет их, а файлы классов с измененными объявлениями исключений совместимы во время выполнения. Безопасность Java VM не улучшена проверенными исключениями, только стиль кодирования.

Ответ 10

Статья Эффективные исключения Java прекрасно объясняет, когда использовать непроверенные и когда использовать проверенные исключения. Вот некоторые цитаты из этой статьи, чтобы выделить основные моменты:

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

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

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

Непредвиденные

  • Считается, что: Часть дизайна
  • Ожидается, что произойдет: Регулярно, но редко
  • Кому это важно: код вверх, вызывающий метод
  • Примеры: альтернативные режимы возврата
  • Наилучшее сопоставление: проверенное исключение

Fault

  • Считается, что: неприятный сюрприз
  • Ожидается, что произойдет: Никогда
  • Кому это нужно: людям, которые должны решить проблему.
  • Примеры: ошибки программирования, аппаратные сбои, ошибки конфигурации, отсутствующие файлы, недоступные серверы
  • Наилучшее сопоставление: исключение без исключения

Ответ 11

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

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

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

Это, конечно же, вопрос предпочтений и навыков разработчика. Оба способа программирования и обработки ошибок могут быть одинаково эффективными (или неэффективными), поэтому я бы не сказал, что есть "Один путь".

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

Ответ 12

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

Говоря об исключениях, я всегда ссылаюсь на Eric Lippert Vexing exceptions в блоге. Он помещает исключения в эти категории:

  • Fatal. Эти исключения не являются вашей ошибкой: вы не можете предотвратить это, и вы не можете разумно справиться с ними. Например, 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, которые злоупотребляют проверенными исключениями.

Ответ 13

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

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

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

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

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

Ответ 14

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

Итак, удаляется исключение Checked, как это было сделано на С#, как тогда можно программно и структурно передать возможность ошибки? Как информировать клиентский код о том, что такие и такие ошибки могут возникать и должны решаться?

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

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

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

Наконец, большинство аргументов, которые я вижу, это "Я не хочу иметь дело с исключениями, многие люди не хотят иметь дело с исключениями. Проверенные исключения заставляют меня обращаться с ними, поэтому я ненавижу проверенное исключение". механизм в целом и отбросить его в пропасть goto ад просто глупо и без энтузиазма и видения.

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

Ответ 15

Андерс говорит о ловушках проверенных исключений и почему он оставил их из С# в эпизоде ​​97 в программном обеспечении радиотехники.

Ответ 16

Проблема

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

Предположим, что мы выполняем простую регистрацию в каждом блоке 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).

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

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

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

Ответ 17

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

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

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

Например:

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

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

Ответ 18

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

Вкратце:

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

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

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

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

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

Ответ 19

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

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

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

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

Автор "ответы":

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

И главная мысль или статья:

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

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

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

Ответ 20

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

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

Ошибки, как правило, возможны в коде, а контейнеры EJB, web и Swing/AWT уже удовлетворяют этому, предоставляя внешний обработчик исключений "сбой запроса". Самая правильная стратегия - откат транзакции и возврат ошибки.

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

Самый большой аргумент против "проверенных" исключений заключается в том, что большинство исключений не может быть исправлено. Простым фактом является то, что у нас нет кода/подсистемы, которая сломалась. Мы не можем видеть реализацию, не отвечаем за нее и не можем ее исправить.

Если наше приложение не является БД, мы не должны пытаться исправить БД. Это нарушит принцип инкапсуляции.

Особенно проблематичными были области JDBC (SQLException) и RMI для EJB (RemoteException). Вместо того, чтобы идентифицировать фиксируемые непредвиденные обстоятельства в соответствии с оригинальной концепцией "проверенного исключения", эти принудительные широко распространенные проблемы системной надежности, а не фактически фиксируемые, должны быть широко объявлены.

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

У нас есть очевидная проблема в Java, требующая тысячи блоков try-catch do-nothing, причем значительная доля (40% +) является ошибочной. Почти ни одна из них не реализует никакой реальной обработки или надежности, но накладывает большие накладные расходы на кодирование.

Наконец, "проверенные исключения" в значительной степени несовместимы с функциональным программированием FP.

Их настойчивость в том, что касается "дескриптора немедленно", несовместима с передовой практикой обработки исключений "catch late" и любой структурой FP, которая абстрагирует циклы/поток управления.

Многие говорят о "обработке" проверенных исключений, но говорят через свои шляпы. Продолжая после неудачи с нулевыми, неполными или неверными данными, чтобы притворяться успехом, ничего не обрабатывает. Это инженерная/надёжная халатность самой низкой формы.

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

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

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

См:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

Ответ 21

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

Другая проблема с проверенными исключениями заключается в том, что они, как правило, неправильно используются. Прекрасным примером этого является java.sql.Connection close(). Он может вызывать SQLException, хотя вы уже явно заявили, что вы сделали это с помощью Подключение. Какую информацию можно закрыть(), возможно, передать, что вам нужно?

Обычно, когда я закрываю() соединение *, он выглядит примерно так:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

Кроме того, не заставляйте меня начинать с различных методов синтаксического анализа и NumberFormatException....NET TryParse, который не генерирует исключений, гораздо проще использовать для него больно возвращаться к Java (мы используем как Java, так и С#, где я работаю).

* В качестве дополнительного комментария соединение PooledConnection Connection.close() даже не закрывает соединение, но вам все равно придется поймать SQLException из-за того, что он является проверенным исключением.

Ответ 22

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

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

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

Ответ 23

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

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

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

Ответ 24

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

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

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

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

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

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

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

Спасибо Java!

Ответ 25

Здесь один аргумент против отмеченных исключений (от joelonsoftware.com):

Мы считаем, что исключения не лучше, чем "goto's", считающиеся вредными с 1960-х годов, поскольку они создают резкий переход от одной точки кода к другой. На самом деле они значительно хуже, чем goto's:

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

Ответ 26

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

С другой стороны, исключения из основного Java API в общем случае должны быть проверены, если они могут обрабатываться когда-либо. Возьмите FileNotFoundException или (мой любимый) InterruptedException. Эти условия должны выполняться практически всегда (т.е. Ваша реакция на InterruptedException не совпадает с вашей реакцией на IllegalArgumentException). Тот факт, что ваши исключения проверены, заставляет разработчиков задуматься о том, является ли состояние обработанным или нет. (Тем не менее, я редко видел, что InterruptedException обрабатывается правильно!)

Еще одна вещь - RuntimeException не всегда "где разработчик получил что-то не так". Исключение незаконного аргумента возникает при попытке создать enum с помощью valueOf и там нет enum этого имени. Это не обязательно ошибка разработчика!

Ответ 27

Хорошее доказывает, что Checked Exception не нужны:

  • Множество фреймворков, которые делают некоторые работы для Java. Подобно Spring, который перекрывает исключение JDBC для исключенных исключений, бросая сообщения в журнал
  • Множество языков, которые появились после java, даже сверху на платформе Java - они не используют их
  • Проверенные исключения, это дословное предсказание о том, как клиент будет использовать код, который генерирует исключение. Но разработчик, который пишет этот код, никогда не узнает о системе и бизнесе, в котором работает клиент кода. В качестве примера используются методы Interfcace, которые вынуждают исключить проверенное исключение. Существует 100 реализаций по системе, 50 или даже 90 реализаций не выбрасывают это исключение, но клиент все равно должен поймать это исключение, если он ссылается на этот интерфейс. Эти 50 или 90 реализаций, как правило, обрабатывают эти исключения внутри себя, помещая исключение в журнал (и это хорошее поведение для них). Что мы должны с этим делать? Мне бы лучше иметь некоторую фоновую логику, которая будет делать всю эту работу - отправку сообщения в журнал. И если я, как клиент кода, почувствовал бы, что мне нужно обработать исключение - я это сделаю. Я могу забыть об этом, правильно, но если я использую TDD, все мои действия будут покрыты, и я знаю, чего я хочу.
  • Еще один пример, когда я работаю с I/O в java, он заставляет меня проверять все исключения, если файл не существует? что я должен с этим делать? Если он не существует, система не перейдет к следующему шагу. Клиент этого метода не получит ожидаемого контента из этого файла - он может обрабатывать Runtime Exception, иначе я должен сначала проверить Checked Exception, поместить сообщение в журнал, а затем исключить исключение из метода. Нет... нет. Лучше сделайте это автоматически с помощью RuntimeEception, который автоматически сделает это/автоматически. Нет никакого смысла обрабатывать его вручную - я был бы счастлив, что увидел сообщение об ошибке в журнале (AOP может помочь с этим.. что-то, что исправляет java). Если, в конце концов, я нахожу, что система должна показывать всплывающее сообщение конечному пользователю - я покажу его, а не проблема.

Я был рад, если java предоставит мне выбор, что использовать при работе с базовыми библиотеками, например I/O. Как и два экземпляра одного класса - один, завернутый в RuntimeEception. Затем мы можем сравнить, что люди будут использовать. На данный момент, однако, многие люди лучше подходят для некоторых фреймворков на Java или на разных языках. Например, Scala, JRuby. Многие просто верят, что СОЛНЦЕ было прав.

Ответ 28

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

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

Ответ 29

Мы видели некоторые ссылки на главного архитектора С#.

Вот альтернативная точка зрения от парня Java о том, когда использовать проверенные исключения. Он признает многие негативы, о которых говорили другие: Эффективные исключения

Ответ 30

Одна важная вещь, о которой никто не упоминал, - это то, как она мешает интерфейсам и лямбда-выражениям.

Предположим, вы определили MyAppException extends Exception. Это исключение верхнего уровня, унаследованное всеми исключениями, брошенными вашим приложением. Каждый метод объявляет throws MyAppException, который является немного нюансом, но управляемым. Обработчик исключений регистрирует исключение и как-то уведомляет пользователя.

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

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