Дело против проверенных исключений

456

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

TofuBeer
источник
46
Я не думаю, что это полностью субъективно - это языковая функция, которая была разработана для конкретного использования, а не для того, чтобы каждый мог решить, для чего он нужен. И это не особенно аргументировано, в нем заранее рассматриваются конкретные опровержения, которые люди могли легко придумать.
Гарет
6
Давай. Рассматриваемая как языковая особенность, эта тема была и может подходить объективно.
Курт Шелфоут
6
@cletus «отвечая на свой вопрос», если бы у меня был ответ, я бы не задавал вопрос!
TofuBeer
8
Отличный вопрос В C ++ нет проверенных исключений вообще, и, на мой взгляд, это делает функцию исключений непригодной. Вы попадаете в ситуацию, когда вам приходится ловить ловушку вокруг каждого вызова функции, который вы делаете, потому что вы просто не знаете, может ли он что-то генерировать.
Дмитрий С.
4
Самый сильный аргумент, который я знаю для проверенных исключений, заключается в том, что их изначально не было в Java, и что, когда они были представлены, они обнаружили сотни ошибок в JDK. Это несколько до Java 1.0. Я лично не был бы без них, и я не согласен с Брюсом Эккелем и другими по этому поводу.
Маркиз Лорн

Ответы:

277

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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, но вы

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

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

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

Я думаю, что открытие файла прекрасно иллюстрирует суть. Программист 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везде разными способами. В частности, я собираюсь написать много кода, как

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

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

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

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

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

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

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

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

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

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

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

B: Клиентские программисты: привыкайте создавать упакованное исключение (Google google) на ранней стадии разработки. JDK 1.4 и более поздние версии предоставляют конструктор 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) */}
Ревень
источник
110
Открытие файлов на самом деле является хорошим примером для абсолютно контрпродуктивных проверенных исключений. Поскольку большую часть времени код, открывающий файл, НЕ МОЖЕТ сделать ничего полезного в отношении исключения - это лучше сделать несколькими слоями стека вызовов. Проверенное исключение вынуждает вас загромождать сигнатуры вашего метода, чтобы вы могли делать то, что сделали бы в любом случае - обрабатывать исключение в том месте, где это наиболее целесообразно.
Майкл Боргвардт
116
@Michael: Согласен, вы, как правило, можете обрабатывать эти исключения ввода-вывода на несколько уровней выше, но я не слышал, чтобы вы отрицали, что как клиент API вы должны ожидать этого. По этой причине я думаю, что проверенные исключения являются подходящими. Да, вам придется объявлять «throws» в каждом стеке вызовов метода up, но я не согласен с тем, что это беспорядок. Это полезная декларативная информация в подписи вашего метода. Вы говорите: мои низкоуровневые методы могут столкнуться с отсутствующим файлом, но они оставят вам обработку этого. Я не вижу ничего, чтобы терять, и только хороший, чистый код, чтобы получить
ревень
23
@Rhubarb: +1, очень интересный ответ, показывает аргументы для обеих сторон. Отлично. О вашем последнем комментарии: обратите внимание, что объявление бросков каждого метода вверх по стеку вызовов не всегда возможно, особенно если вы реализуете интерфейс на этом пути.
Р. Мартиньо Фернандес
8
Это очень сильный аргумент (и я согласен с ним в целом), но есть случай, когда он не работает: общие обратные вызовы. Если у меня есть какая-то библиотека, вызывающая некоторый пользовательский код, то я не знаю (как я знаю, в java), чтобы эта библиотека распространяла проверенные исключения из кода пользователя, который она вызывает, в код пользователя, который ее вызывает. Эта проблема также распространяется на другие части системы типов многих статически типизированных языков, которые не поддерживают надлежащие универсальные типы. Этот ответ будет улучшен путем упоминания об этом (и, возможно, ответ).
Mankarse
7
Неправильно @Rhubarb. Проверенные исключения были предназначены для «непредвиденных обстоятельств», которые можно и нужно обрабатывать, но они всегда были несовместимы с лучшей практикой «бросай рано, лови поздно». Открытие файла - пример, подобранный вишней, с которым можно справиться. Большинство IOException (например, запись байта), SQLException, RemoteException невозможно обработать осмысленным образом. Отказ или повторная попытка должны быть на уровне «бизнес» или «запрос», а проверенные исключения - используемые в библиотеках Java - являются ошибкой, которая делает это трудным. literatejava.com/exceptions/...
Thomas W
184

Суть проверяемых исключений в том, что они не являются исключениями при обычном понимании концепции. Вместо этого они являются альтернативными возвращаемыми значениями 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? На любом другом языке я могу допустить исключение ввода-вывода, и если я ничего не сделаю для его обработки, мое приложение остановится, и я получу удобную трассировку стека для просмотра. Это лучшее, что может случиться.

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

  1. излишне увеличивает сцепление;
  2. делает подписи интерфейса очень хрупкими для изменения;
  3. делает код менее читабельным;
  4. Это настолько раздражает, что обычная реакция программиста - победить систему, выполнив что-то ужасное, например, «throws Exception», «catch (Exception e) {}» или завернув все в RuntimeException (что затрудняет отладку).

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

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

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

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

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

bobince
источник
14
В вашем примере кода было бы лучше объединить исключения, чтобы можно было найти исходную причину при чтении журналов: throw CanNeverHappenException (e);
Эско Луонтола
5
Я не согласен. Исключения, проверенные или нет, являются исключительными условиями. Пример: метод, который извлекает объект через HTTP. Возвращаемое значение - это еще объект или ничто, все вещи, которые могут испортиться, являются исключительными. Обработка их как возвращаемых значений, как это делается в C, приводит только к путанице и плохому дизайну.
Мистер Смит
15
@Mister: Я хочу сказать, что проверенные исключения, реализованные в Java, на практике ведут себя больше как возвращаемые значения, как в C, чем как традиционные «исключения», которые мы могли бы узнать из C ++ и других языков до Java. И это IMO действительно приводит к путанице и плохому дизайну.
bobince
9
Согласитесь, что злоупотребление стандартными библиотеками проверенных исключений определенно усугубляет путаницу и плохое поведение при отлове. И часто это просто из-за плохой документации, например, метода разрыва, такого как disconnect (), который выдает IOException, когда «возникает какая-то другая ошибка ввода-вывода». Ну, я отключился! Я протекаю ручку или другой ресурс? Мне нужно повторить попытку? Не зная, почему это произошло, я не могу получить действие, которое я должен предпринять, и поэтому я должен догадаться, должен ли я просто проглотить это, повторить попытку или поручиться.
charstar
14
+1 за «альтернативные возвращаемые значения API». Интересный взгляд на проверенные исключения.
Жолт Тёрёк
75

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

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
Но если исключение правильно перехвачено, вы можете сообщить пользователю, выполнить другие задачи (войти?) И выйти из приложения контролируемым образом. Я согласен, что некоторые части API могли бы быть разработаны лучше. И по причине ленивого программиста, я думаю, как программист, вы на 100% ответственны за свой код.
Мистер Смит
3
обратите внимание, что try-catch-rethrow позволяет вам указать сообщение - я обычно использую его для добавления информации о содержимом переменных состояния. Частый пример - исключения IOException для добавления absolutePathName () рассматриваемого файла.
Турбьёрн Равн Андерсен
15
Я думаю, что IDE, такие как Eclipse, могут винить в том, сколько раз вы видели пустой блок catch. Действительно, они должны перебрасывать по умолчанию.
artbristol
12
«99% времени я ничего не могу с этим поделать» - неправильно, вы можете показать пользователю сообщение «Не удалось подключиться к серверу» или «Произошел сбой устройства ввода-вывода», вместо того, чтобы просто позволить приложению аварийно завершить работу. из-за небольшого сбоя сети. Оба ваших примера являются искусством работы плохих программистов. Вы должны атаковать плохих программистов и сами не проверять исключения. Это похоже на то, что я нападаю на инсулин за то, что я не помогаю с диабетом, когда я использую его как заправку для салата.
AxiomaticNexus
2
@YasmaniLlanes Вы не можете всегда делать эти вещи. Иногда у вас есть интерфейс, чтобы придерживаться. И это особенно верно, когда вы разрабатываете хорошие поддерживаемые API, потому что вы не можете просто начать создавать побочные эффекты повсюду. И это, и сложность, которую он представит, сильно укусят вас в больших масштабах. Так что да, в 99% случаев с этим ничего не поделаешь.
MasterMastic
50

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

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

Возьми добрую Яву 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 сам мог бы обработать исключение (как я объясню позже).

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

@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 s через границы полиморфных методов. Это доказывает мою точку зрения!))

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

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

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

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@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);
    }
}

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

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«S writeметоды все указать throws IOException. ByteArrayOutputStreamявляется подклассом, который пишет в массив в памяти вместо истинного источника ввода / вывода. Его переопределенные writeметоды не могут вызывать IOExceptions, поэтому они не имеютthrows предложения, и вы можете вызывать их, не беспокоясь о требовании «поймать или указать».

Кроме не всегда. Предположим, что 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 , но вы хотите изменить объявленный тип переменной на a ByteArrayOutputStream, компилятор будет ругать вас за попытку отловить проверенное исключение, которое, по его словам, не может быть выброшено.

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

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


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

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

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

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

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

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

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

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

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

Boann
источник
Для вашего метода размера с помощью WidgetList я бы кешировал размер в переменной и установил его в конструкторе. Конструктор может создать исключение. Это не будет работать, если файл изменяется при использовании WidgetList, что, вероятно, будет плохо, если это произойдет.
TofuBeer
2
SomeStupidExceptionOmgWhoCare хорошо кто-то заботился достаточно, чтобы бросить его. Так что либо он никогда не должен был быть брошен (плохой дизайн), либо вы должны действительно справиться с этим. То же самое относится и к плохой реализации класса до 1.0 (выходной поток байтового массива), где дизайн был, к сожалению, плохим.
TofuBeer
2
Правильная идиома была бы директивой, которая ловила бы любые указанные исключения, вызванные вложенными вызовами подпрограмм, и отбрасывала бы их, завернутые в a RuntimeException. Обратите внимание, что подпрограмма может быть одновременно объявлена ​​как throws IOExceptionи, в то же время, также указывать, что любой IOExceptionвыброс из вложенного вызова следует считать неожиданным и перенесенным.
суперкат
15
Я профессиональный разработчик C # с некоторым опытом Java, который наткнулся на этот пост. Я озадачен тем, почему кто-то поддержит это странное поведение. В .NET, если я хочу перехватить определенный тип исключения, я могу это перехватить. Если я просто хочу, чтобы его выбросили в стек, ничего не поделаешь. Я бы хотел, чтобы Java не была такой причудливой. :)
aikeru
Что касается «иногда я закомментирую вызов метода временно» - я научился использовать if (false)для этого. Это позволяет избежать проблемы с предложением броска, а предупреждение помогает мне быстрее вернуться назад. +++ Тем не менее, я согласен со всем, что вы написали. Проверенные исключения имеют некоторое значение, но это значение незначительно по сравнению с их стоимостью. Почти всегда они просто мешают.
Maaartinus
45

Ну, речь не идет об отображении трассировки стека или тихом сбое. Речь идет о возможности сообщать об ошибках между слоями.

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

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

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

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

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

  1. Поймать и обработать конкретное исключение. Это для реализации логики повторения, например.
  2. Поймай и отбрось другие исключения. Все, что здесь происходит, это, как правило, ведение журнала, и обычно это банальное сообщение типа «Невозможно открыть $ filename». Это ошибки, с которыми ничего не поделаешь; только более высокие уровни знают достаточно, чтобы справиться с этим.
  3. Поймай все и покажи сообщение об ошибке. Обычно это находится в самом корне диспетчера, и все, что он делает, это гарантирует, что он может сообщить об ошибке вызывающей стороне через механизм не-исключения (всплывающий диалог, маршалинг объекта RPC-ошибки и т. Д.).
Ричард Левассер
источник
5
Вы могли бы сделать определенные подклассы AppSpecificException, чтобы разрешить разделение, сохраняя при этом простые сигнатуры методов.
Торбьерн Равн Андерсен
1
Также очень важным дополнением к пункту 2 является то, что он позволяет ДОБАВИТЬ ИНФОРМАЦИЮ в обнаруженное исключение (например, путем вложения в RuntimeException). Гораздо лучше иметь имя файла, не найденного в трассировке стека, чем скрытое глубоко в файле журнала.
Торбьерн Равн Андерсен
1
По сути, ваш аргумент «Управление исключениями утомляет, поэтому я бы предпочел не заниматься этим». Поскольку исключение всплывает, оно теряет смысл, а создание контекста практически бесполезно. Как разработчик API, вы должны четко договориться о том, что можно ожидать, когда что-то пойдет не так, если моя программа падает, потому что я не был проинформирован о том, что то или иное исключение может «всплыть», тогда вы, как дизайнер, потерпели неудачу и как В результате вашего отказа моя система не так стабильна, как могла бы быть.
Newtopian
4
Это совсем не то, что я говорю. Ваше последнее предложение на самом деле согласен со мной. Если все обернуто в AppSpecificException, то оно не всплывает (и смысл / контекст теряется), и, да, клиент API не информируется - это именно то, что происходит с проверенными исключениями (как в Java) потому что люди не хотят иметь дело с функциями с большим количеством throwsобъявлений.
Ричард Левассер
6
@Newtopian - исключения могут в основном обрабатываться только на уровне «бизнес» или «запрос». Имеет смысл потерпеть неудачу или повторить попытку с большой степенью детализации, а не для каждого крошечного потенциального сбоя. По этой причине передовая практика обработки исключений обобщается как «бросай рано, лови поздно». Проверенные исключения затрудняют управление надежностью на правильном уровне и поощряют огромное количество неправильно кодированных блоков улова. literatejava.com/exceptions/...
Thomas W
24

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);
}

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

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, проверенные исключения начинают становиться действительно раздражающими и бесполезными.

Побег из тюрьмы

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

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

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

Вывод

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

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

Люк Куинан
источник
3
В этом примере и Java, и C # просто позволяют распространяться исключениям, не обрабатывая их (Java через IllegalStateException). Разница в том, что вы можете обрабатывать исключение FileNotFoundException, но маловероятно, что обработка InvocationTargetException или InterruptedException будет полезной.
Люк Куинэйн
3
А как в C # узнать, что может возникнуть исключение ввода-вывода? Также я бы никогда не бросил исключение из run ... Я считаю, что злоупотребление обработкой исключений. Извините, но для этой части вашего кода я просто пока вижу вашу сторону.
TofuBeer
7
Мы добираемся туда :-) Итак, с каждым новым выпуском API вы должны прочесывать все ваши вызовы и искать какие-либо новые исключения, которые могут произойти? Это легко может случиться с внутренними API-интерфейсами компании, поскольку им не нужно беспокоиться о обратной совместимости.
TofuBeer
3
Вы имели в виду уменьшение отношения сигнал / шум?
neo2862
3
@ TofuBeer Не принуждают ли вы обновлять ваш код после того, как изменился интерфейс нижележащего API? Если бы у вас были только непроверенные исключения, вы бы получили сломанную / неполную программу, не зная об этом.
Франсуа Буржуа
23

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

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

Le Dude
источник
2
На самом деле главный архитектор.
Ловушка
18
Я прочитал это, для меня его аргумент сводится к тому, что «там плохие программисты».
TofuBeer
6
TofuBeer, совсем нет. Все дело в том, что часто вы не знаете, что делать с Исключением, которое выдает вызываемый метод, и случай, который вас действительно интересует, даже не упоминается. Вы открываете файл, вы получаете IO Exception, например ... это не моя проблема, поэтому я выбрасываю его. Но вызывающий метод верхнего уровня просто захочет остановить обработку и сообщить пользователю, что существует неизвестная проблема. Проверенное исключение совсем не помогло. Это была одна из миллиона странных вещей, которые могут случиться.
Дэн Розенстарк
4
@yar, если вам не нравится проверенное исключение, то создайте «throw new RuntimeException» (мы не ожидали этого при выполнении Foo.bar () «, e)» и покончим с этим.
Торбьерн Равн Андерсен
4
@ ThorbjørnRavnAndersen: фундаментальный недостаток дизайна в Java, который, к сожалению, скопирован .net, заключается в том, что он использует тип исключения в качестве основного средства для принятия решения, следует ли действовать, и основного средства указания общего типа вещи. что пошло не так, когда на самом деле эти две проблемы в значительной степени ортогональны. Важно не то, что пошло не так, а то, в чем состоят объекты состояния. Кроме того, и .net, и Java по умолчанию предполагают, что действие и устранение исключения, как правило, одно и то же, хотя на самом деле они часто разные.
суперкат
20

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

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

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

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

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

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

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

tsimon
источник
Java рекомендует вам делать такие вещи, чтобы вам не приходилось добавлять исключения каждого типа в каждую сигнатуру метода.
yfeldblum
17
Забавно ... с тех пор, как я правильно обнял проверенные исключения и использовал их должным образом, мои программы перестали взрываться в огромной дымящейся куче недовольных клиентов. Если во время разработки у вас будет большой уродливый плохой след стека, то клиент обязательно получит их. Хочу увидеть его лицо, когда он видит ArrayIndexOutOfBoundsException с трассировкой стека высотой в милю в своей сбойной системе вместо небольшого уведомления в трее, говорящего о том, что цветовая конфигурация для кнопки XYZ не может быть проанализирована, поэтому вместо этого использовалось программное обеспечение по умолчанию, а программное обеспечение счастливо гудело вместе
ньютопия
1
Возможно, то, что нужно Java, - это объявление «cantHandle», которое указывало бы, что метод или блок кода try / catch не подготовлен для обработки конкретного исключения, которое происходит внутри него, и что любое такое исключение, которое происходит с помощью средств, отличных от явного throw в этом методе (в отличие от вызываемого метода) должен автоматически переноситься и перебрасываться в RuntimeException. ИМХО, проверенные исключения должны редко распространяться вверх по стеку вызовов без переноса.
суперкат
3
@Newtopian - я пишу серверное и высоконадежное программное обеспечение и занимаюсь этим уже 25 лет. Мои программы никогда не взрывались, и я работаю с финансовыми и военными системами на основе высокой доступности, повторных попыток и повторного подключения. У меня есть абсолютная объективная основа, чтобы предпочесть исключения во время выполнения. Проверенные исключения затрудняют правильную практику «брось рано, поймай поздно». Надежная надежность и обработка ошибок находятся на уровне «бизнес», «соединение» или «запрос». (Или иногда при разборе данных). Проверенные исключения мешают делать это правильно.
Томас В.
1
Исключения, о которых вы здесь говорите, это то, RuntimeExceptionsчто вам действительно не нужно ловить, и я согласен, что вы должны позволить программе взорваться. Исключения, которые вы всегда должны ловить и обрабатывать, - это проверенные исключения IOException. Если вы получите IOException, в вашем коде нечего исправить; Ваша программа не должна взорваться только потому, что произошел сбой сети.
AxiomaticNexus
20

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

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

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

(ТАК не позволяет таблицы, поэтому вы можете прочитать следующее с исходной страницы ...)

непредвиденные обстоятельства

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

Fault

  • Считается: неприятный сюрприз
  • Ожидается, что случится: никогда
  • Кого это волнует: людей, которым нужно решить проблему
  • Примеры: ошибки программирования, аппаратные сбои, ошибки конфигурации, отсутствующие файлы, недоступные серверы
  • Best Mapping: непроверенное исключение
Эско Луонтола
источник
Я знаю, когда их использовать, я хочу знать, почему люди, которые не следуют этому совету ... не следуют этому совету :-)
TofuBeer
Что такое ошибки программирования и как отличить их от ошибок использования ? Это программная ошибка, если пользователь передает неверные аргументы программе? С точки зрения Java это может быть не программная ошибка, но с точки зрения сценария оболочки это программная ошибка. Так в чем же неверные аргументы args[]? Это непредвиденный случай или ошибка?
празднование
3
@TofuBeer - Поскольку разработчики библиотеки Java, решили поставить все виды сбоев неустранимых низкого уровня , как проверяемые исключения , когда они явно должны были бесконтрольно . FileNotFound является единственным IOException, который должен быть проверен, например. Что касается JDBC - только соединение с базой данных, можно разумно считать непредвиденным обстоятельством . Все остальные исключения SQLE должны были быть неудачными и непроверенными. Обработка ошибок должна корректно выполняться на уровне «бизнес» или «запрос» - см. Рекомендации «Брось раньше, поймай поздно». Проверенные исключения являются препятствием для этого.
Томас В.
1
В вашем аргументе есть один огромный недостаток. «Непредвиденные обстоятельства» НЕ должны обрабатываться посредством исключений, а должны возвращаться через бизнес-код и возвращаемые значения методов. Исключения составляют, как говорится в слове, ИСКЛЮЧИТЕЛЬНЫЕ ситуации, следовательно, недостатки.
Маттео Моска
@MatteoMosca Коды возврата ошибок, как правило, игнорируются, и этого достаточно, чтобы их дисквалифицировать. На самом деле, все необычное часто может быть обработано только где-то в стеке, и это пример использования исключений. Я мог бы представить что-то вроде File#openInputStreamвозвращения Either<InputStream, Problem>- если ты это имеешь в виду, тогда мы можем согласиться.
Maaartinus
19

Короче говоря:

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

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

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

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

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

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

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

Так зачем?

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

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

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

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

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

Дэвид Лихтеблау
источник
4
Ваш аргумент противоречит самому себе. Если «иногда вам нужно поймать исключение» и качество API часто плохое, без проверенных исключений вы не будете знать, не забыл ли разработчик задокументировать, что определенный метод выдает исключение, которое необходимо перехватить. Соедините это с броском, AcmeExceptionа не с AcmeFileNotFoundErrorудачей, выясняя, что вы сделали неправильно, и где вам нужно поймать это. Проверенные исключения предоставляют программистам некоторую защиту от плохого дизайна API.
Ева
1
Дизайн библиотеки Java допустил серьезные ошибки. «Проверенные исключения» предназначались для предсказуемых и восстанавливаемых непредвиденных обстоятельств - таких как файл не найден, сбой соединения. Они никогда не предназначались или не подходили для системного сбоя низкого уровня. Было бы неплохо принудительно проверить открытие файла, но нет разумной повторной попытки или восстановления из-за невозможности записать один байт / выполнить SQL-запрос и т. Д. Повторная попытка или восстановление корректно обрабатываются по запросу "business" или " Уровень, на котором проверяются исключения, делать бессмысленно сложно. literatejava.com/exceptions/…
Томас В.
17

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

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

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

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

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

Марио Ортегон
источник
6
Я делаю, чтобы Для меня они являются неотъемлемой частью контракта. Не прибегая к подробностям документации API, я могу быстро узнать наиболее вероятные сценарии ошибок.
Уэйн Хартман
2
Согласен. Однажды я испытал необходимость проверенных исключений в .Net, когда пытался совершать сетевые вызовы. Зная, что сбой в сети может произойти в любой момент, мне пришлось прочитать всю документацию API, чтобы выяснить, какое исключение мне нужно было отловить специально для этого сценария. Если бы C # проверил исключения, я бы знал об этом немедленно. Другие разработчики C #, вероятно, просто допустили бы сбой приложения из-за простой сетевой ошибки.
AxiomaticNexus
13

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

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

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

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

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

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

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

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

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

Проблемы API

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

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

Вывод

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

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

Даниэль А.А. Пельсмакер
источник
И злоупотреблять ими, не имея иерархии.
TofuBeer
1
FileNotFound и установление JDBC / сетевого подключения являются непредвиденными и корректными для проверки исключениями, поскольку они предсказуемы и (возможно) восстанавливаемы. Большинство других IOExceptions, SQLExceptions, RemoteException и т. Д. Являются непредсказуемыми и неисправимыми сбоями и должны быть исключениями времени выполнения. Из-за ошибочного дизайна библиотеки Java, мы все были в замешательстве с этой ошибкой и теперь в основном используем Spring & Hibernate (которые правильно разработали свой дизайн).
Томас В.
Вы обычно должны обрабатывать исключения с тупыми головами, хотя можете не называть это «обработкой». Например, на веб-сервере я регистрирую их и показываю пользователю 500. Поскольку исключение является неожиданным, есть все, что я могу сделать до исправления ошибки.
Maaartinus
6

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

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

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

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

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

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

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

Newtopian
источник
2
Проверенные исключения были бы полезны, если бы существовало декларативное средство сказать, что ни один из вызовов методов внутри блока не должен генерировать некоторые (или любые) проверенные исключения, и любые такие исключения должны автоматически переноситься и перебрасываться. Они могут быть еще более полезными, если вызовы методов, которые были объявлены как выбрасывающие проверенные исключения, обмениваются скоростью вызова / возвратом для скорости обработки исключений (так, чтобы ожидаемые исключения могли обрабатываться почти так же быстро, как и обычный программный поток). Однако в настоящее время ни одна из этих ситуаций не применима.
суперкат
6

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

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

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

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

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

Курт Шелфтоут
источник
5

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

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

Какие из них являются «проверенными» исключениями? Throwables являются проверенными исключениями, за исключением случаев, когда они также являются Errors, которые являются непроверенными исключениями, а затем есть Exceptions, которые также являются Throwables и являются основным типом проверяемого исключения, за исключением того, что есть и одно исключение, которое заключается в том, что если они также RuntimeExceptionс, потому что это другой вид непроверенного исключения.

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

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

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

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

Спасибо Java!

Boann
источник
2
Насколько я могу сказать, в этом ответе говорится, что «исключения Java - беспорядок» в полной иронии, возможно, забавным образом. Кажется, что этого не происходит, это объясняют, почему программисты стараются не пытаться понять, как эти вещи должны работать. Кроме того, в реальных случаях (по крайней мере, в тех, с которыми мне довелось иметь дело), ​​если программисты не пытаются сознательно усложнить свою жизнь, исключения далеко не так сложны, как вы описали.
CptBartender
5

Проблема

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

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

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

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

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

Решение

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

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

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

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

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

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

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

Петр Собчик
источник
6
Исключения придуманы, чтобы сделать что-то полезное с ними. Запись их в файл журнала или рендеринг красивого окна бесполезна, потому что первоначальная проблема не решается этим. Чтобы сделать что-то полезное, нужно попробовать другую стратегию решения. Примеры: Если я не могу получить свои данные с сервера AI, попробуйте их на сервере B. Или, если алгоритм A вызывает переполнение кучи, я пробую алгоритм B, который работает намного медленнее, но может быть успешным.
празднование
2
@ceving Да, в теории все хорошо и верно. Но теперь давайте вернемся к практике слова. Пожалуйста, ответьте честно, как часто вы делаете это в своем проекте в реальном мире? Какая часть catchблоков в этом реальном проекте делает что-то действительно «полезное» с исключением? 10% было бы хорошо. Обычные проблемы, которые генерируют исключения, похожи на попытку чтения конфигурации из несуществующего файла, OutOfMemoryErrors, NullPointerExceptions, ошибки целостности ограничений базы данных и т. Д., И т. Д. Вы действительно пытаетесь корректно восстановить все из них? Я тебе не верю :). Часто просто нет возможности восстановиться.
Петр Собчик,
3
@PiotrSobczyk: Если программа выполняет какое-либо действие в результате запроса suer, и операция каким-то образом завершается с ошибкой, которая ничего не повредила в состоянии системы, уведомление пользователя о невозможности завершения операции является весьма полезным способ справиться с ситуацией. Самая большая ошибка исключений в C # и .net заключается в том, что нет единого способа выяснить, не было ли что-либо в состоянии системы повреждено.
суперкат
4
Правильно, @PiotrSobczyk. Чаще всего единственное правильное действие, предпринимаемое в ответ на исключение, - это откат транзакции и возврат ответа об ошибке. Идеи «решения исключений» подразумевают знание и авторитет, которого у нас нет (и не должно быть), и нарушают инкапсуляцию. Если наше приложение не является БД, мы не должны пытаться исправить БД. Чистый сбой и избегание записи ошибочных данных, ceving, достаточно полезен .
Томас В.
@PiotrSobczyk Вчера я имел дело с исключением «не удалось прочитать объект» (которое возникнет только потому, что базовая база данных была обновлена ​​до программного обеспечения - что никогда не должно происходить, но это возможно из-за человеческой ошибки) при переходе на Историческая версия базы данных гарантированно указывает на старую версию объекта.
одноименный
4

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

Чак Конвей
источник
4

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

В итоге:

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

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

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

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

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

Джошуа
источник
1
Вы должны иметь что-то вроде DAGNodeException в интерфейсе, затем перехватить IOException и преобразовать его в DAGNodeException: public void call (DAGNode arg) throws DAGNodeException;
TofuBeer
2
@TofuBeer, это точно моя точка зрения. Я считаю, что постоянное обертывание и развертывание исключений хуже, чем удаление отмеченных исключений.
Джошуа
2
Ну, тогда мы полностью не согласны ... но ваша статья по-прежнему не дает ответа на реальный вопрос о том, как вы не позволяете приложению отображать трассировку стека для пользователя при возникновении исключения во время выполнения.
TofuBeer
1
@TofuBeer - когда это терпит неудачу, говорить пользователю, что он потерпел неудачу, правильно! Какая у вас альтернатива, кроме как «замаскировать» ошибку с «нулевыми» или неполными / неверными данными? Притворяться, что это удалось, - ложь, которая только ухудшает положение. Имея 25-летний опыт работы в высоконадежных системах, логика повторения должна использоваться только осторожно и там, где это необходимо. Я также ожидал бы, что посетитель, скорее всего, снова потерпит неудачу, независимо от того, сколько раз вы будете повторять попытку. Если вы не управляете самолетом, переключение на вторую версию того же алгоритма нецелесообразно и неправдоподобно (и может в любом случае потерпеть неудачу).
Томас В.
4

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

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

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

Например:

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

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

Дэйв Элтон
источник
2
Точно. Легкий и понятный способ обработки исключений. Как говорит Дейв, правильная обработка исключений обычно выполняется на высоком уровне . «Брось рано, поймай поздно» - это принцип. Проверенные исключения затрудняют это.
Томас W
4

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

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

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

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

Автор "Отзывы":

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

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

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

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

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

Петр Собчик
источник
4

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Смотрите:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

Томас В.
источник
1
Моя точка зрения состоит в том, чтобы как следует потерпеть неудачу в качестве общей стратегии. Непроверенные исключения помогают этому, поскольку они не заставляют блоки захвата вставляться. После этого перехват и запись ошибок могут быть оставлены на усмотрение нескольких внешних обработчиков, а не ошибочно кодированы тысячи раз по всей базе кода (что на самом деле скрывает ошибки) . Для произвольных сбоев непроверенные исключения являются абсолютно наиболее правильными. Непредвиденные обстоятельства - предсказуемые результаты, такие как нехватка средств - являются единственными законными исключениями, которые стоит проверить.
Томас В.
1
Мой ответ уже выше обращается к этому. Прежде всего, 1) Внешний обработчик ошибок должен перехватить все. Кроме того, только для определенных идентифицированных сайтов, 2) Конкретные ожидаемые непредвиденные обстоятельства могут быть обнаружены и обработаны - на ближайшем сайте они выбрасываются. Это означает, что файл не найден, недостаточно средств и т. Д. В момент, когда они могут быть восстановлены - не выше. Принцип инкапсуляции означает, что внешние слои не могут / не должны нести ответственность за понимание / восстановление после сбоев глубоко внутри. В-третьих, 3) все остальное должно быть выброшено наружу - по возможности не проверено.
Томас В.
1
Самый внешний обработчик перехватывает Exception, регистрирует его и возвращает «сбойный» ответ или отображает диалоговое окно с сообщением об ошибке. Очень просто, совсем не сложно определить. Дело в том, что каждое исключение, которое может быть восстановлено не сразу и локально, является неисправимым отказом из-за принципа инкапсуляции. Если код, о котором нужно знать, не может его восстановить, тогда запрос в целом завершается неудачно и правильно. Это правильный способ сделать это правильно.
Томас В.
1
Неправильно. Задача внешнего обработчика состоит в том, чтобы аккуратно завершать работу с ошибками и регистрировать ошибки на границе запроса. Сломанный запрос завершается неудачно, об исключении сообщается, поток может продолжить обслуживание следующего запроса. Такой внешний обработчик является стандартной функцией в контейнерах Tomcat, AWT, Spring, EJB и «основном» потоке Java.
Томас В.
1
Почему опасно сообщать о «подлинных ошибках» на границе запроса или крайнем обработчике ??? Я часто работаю в области системной интеграции и надежности, где правильное проектирование надежности действительно важно, и использую подходы «непроверенного исключения» для этого. Я не совсем уверен, что вы на самом деле обсуждаете - кажется, что вы, возможно, захотите провести 3 месяца в неконтролируемом порядке исключения, почувствовать это, а затем, возможно, мы сможем обсудить дальше. Спасибо.
Томас W
4

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

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

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

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

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

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

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

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

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

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

Powerlord
источник
2
Правильно, любой из драйверов может ... вопрос скорее "почему это должно заботить программиста?" как он все равно получил доступ к базе данных. Документы даже предупреждают вас, что вы всегда должны фиксировать () или откатывать () текущую транзакцию перед вызовом close ().
Powerlord
Многие люди думают, что закрытие файла не может вызвать исключение ... stackoverflow.com/questions/588546/… Вы на 100% уверены, что нет случаев, когда это будет иметь значение?
TofuBeer
Я на 100% уверен, что нет случаев, когда это будет иметь значение, и что вызывающий не будет пытаться поймать.
Мартин
1
Отличный пример с закрытием связей, Мартин! Я могу только перефразировать вас: если мы просто явно заявили, что мы закончили с соединением, зачем беспокоиться о том, что происходит, когда мы его закрываем. Есть еще такие случаи, когда программисту все равно, происходит ли исключение, и он абсолютно прав.
Петр Собчик
1
@PiotrSobczyk: некоторые драйверы SQL будут кричать, если кто-то закрывает соединение после запуска транзакции, но не подтверждает и не откатывает его. ИМХО, кричать лучше, чем молчаливо игнорировать проблему, по крайней мере в тех случаях, когда крики не приводят к потере других исключений.
суперкат
3

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

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

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

Александр Дубинский
источник
2

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

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

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

oxbow_lakes
источник
1
Да, это ошибка разработчика. Они явно не использовали правильное имя, поэтому они должны вернуться и исправить свой код.
AxiomaticNexus
@AxiomaticNexus Ни один здравомыслящий разработчик не использует enumимена членов просто потому, что enumвместо них они используют объекты. Таким образом, неправильное имя может быть получено извне, будь то файл импорта или что-то еще. Один из возможных способов справиться с такими именами - звонить MyEnum#valueOfи ловить IAE. Другой способ - использовать предварительно заполненные Map<String, MyEnum>, но это детали реализации.
Maaartinus
@maaartinus В некоторых случаях имена членов enum используются без внешней строки. Например, когда вы хотите, чтобы динамически проходить через все элементы, чтобы что-то делать с каждым. Кроме того, неважно, пришла ли строка извне или нет. Разработчик имеет всю необходимую информацию, чтобы знать, приведет ли передача строки x к «MyEnum # valueOf», перед передачей ошибки. Передача x строки в «MyEnum # valueOf» в любом случае, когда это вызвало бы ошибку, явно была бы ошибкой со стороны разработчика.
AxiomaticNexus
2

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

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

  • Они невидимы в исходном коде. Глядя на блок кода, включая функции, которые могут генерировать или не генерировать исключения, нет никакого способа увидеть, какие исключения могут генерироваться и откуда. Это означает, что даже тщательная проверка кода не выявляет потенциальных ошибок.
  • Они создают слишком много возможных точек выхода для функции. Чтобы написать правильный код, вам действительно нужно продумать каждый возможный путь кода в вашей функции. Каждый раз, когда вы вызываете функцию, которая может вызвать исключение и не перехватить его на месте, вы создаете возможности для неожиданных ошибок, вызванных внезапно завершившимися функциями, оставив данные в несогласованном состоянии или другими путями кода, которые вы не сделали. подумать о.
finnw
источник
3
+1 Возможно, вы захотите обобщить аргумент в своем ответе, хотя? Они похожи на невидимых gotos и ранних выходов для ваших процедур, разбросанных по всей программе.
MarkJ
13
Это больше аргумент против исключений в целом.
Ionuț G. Stan
10
Вы действительно читали статью! Во-первых, он говорит об исключениях в целом, во-вторых, раздел «Они невидимы в исходном коде» относится конкретно к исключению UNCHECKED. В этом весь смысл проверенного исключения ... так что вы ЗНАЕТЕ, какой код выдает, что куда
Newtopian
2
@ Ева Они не одинаковы. С оператором goto вы можете увидеть gotoключевое слово. С помощью цикла вы можете увидеть закрывающую скобку breakили continueключевое слово или . Все они переходят к точке текущего метода. Но вы не всегда можете видеть throw, потому что часто это не в текущем методе, а в другом методе, который он вызывает (возможно косвенно)
finnw
5
@finnw Функции сами по себе являются формой перехода. Вы обычно не знаете, какие функции вызывают функции, которые вы вызываете. Если вы программируете без функций, у вас не будет проблем с невидимыми исключениями. Это означает, что проблема не связана конкретно с исключениями и не является допустимым аргументом против исключений в целом. Вы могли бы сказать, что коды ошибок быстрее, вы могли бы сказать, что монады чище, но аргумент goto глуп.
Ева
2

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

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

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

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

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

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

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

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

Vlasec
источник
1

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

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

Джейкоб Торонто
источник
2
Проблема с проверенными исключениями в Java проистекает из более глубокой проблемы, заключающейся в том, что слишком много информации инкапсулируется в ТИПе исключения, а не в свойствах экземпляра. Было бы полезно иметь проверенные исключения, если «проверено» было атрибутом сайтов броска / перехвата, и если можно было декларативно указать, должно ли проверенное исключение, которое выходит из блока кода, оставаться проверенным исключением или быть замеченным любым включающим блоком в качестве непроверенного исключения; аналогично, блоки catch должны иметь возможность указывать, что им нужны только проверенные исключения.
суперкат
1
Предположим, что подпрограмма поиска в словаре определена для того, чтобы генерировать исключение определенного типа, если была предпринята попытка получить доступ к несуществующему ключу. Для клиентского кода может быть разумным поймать такое исключение. Однако, если какой-то метод, используемый процедурой поиска, генерирует исключение того же типа таким образом, которого процедура поиска не ожидает, клиентский код, вероятно, не должен его перехватывать. Если флажок check-ness будет свойством экземпляров исключений , сайтов-бросков и сайтов-ловушек, это позволит избежать таких проблем. Клиент будет ловить «проверенные» исключения этого типа, избегая неожиданных.
суперкат
0

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

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

Проект, над которым я работаю, берет много библиотек и интегрирует их в один API, API, который полностью основан на непроверенных исключениях. Эти платформы предоставляют высокоуровневый API, который в начале был полон проверенных исключений и имел только несколько непроверенных исключения (Initialization Exception, ConfigurationException и т. д.) и, надо сказать, был не очень дружелюбным . Большую часть времени вам приходилось ловить или перебрасывать исключения, которые вы не знаете, как обрабатывать, или вас это даже не волнует (не путать с вами следует игнорировать исключения), особенно на стороне клиента, где один клик может вызвать 10 возможных (проверенных) исключений.

Текущая версия (3-я версия) использует только непроверенные исключения и имеет глобальный обработчик исключений, который отвечает за обработку всего необработанного. API предоставляет способ регистрации обработчиков исключений, который решает, будет ли исключение считаться ошибкой (в большинстве случаев это так), что означает запись и уведомление кого-либо, или это может означать что-то другое - например, это исключение AbortException, что означает прерывание текущего потока выполнения и не регистрировать ошибки, потому что это нежелательно. Конечно, для того, чтобы отработать все пользовательские потоки, необходимо обработать метод run () с помощью try {...} catch (all).

public void run () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

В этом нет необходимости, если вы используете WorkerService для планирования заданий (Runnable, Callable, Worker), который обрабатывает все за вас.

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

adrian.tarau
источник