Зачем нам нужна обработка исключений?
Я могу проверить вход и если это недопустимый ввод от пользователя, я могу использовать простое условие "if", которое печатает "вход недействителен, пожалуйста, заново введите" (в случае наличия недопустимого ввода).
Этот подход "если есть потенциал для отказа, проверьте его с помощью условия if, а затем укажите правильное поведение при возникновении сбоя..." для меня достаточно.
Если я могу в принципе покрыть любой отказ (деление на ноль и т.д.) С этим подходом, зачем мне нужен весь этот механизм обработки исключений (класс исключения и объекты, проверенные и непроверенные и т.д.)?
Ответы
Ответ 1
Предположим, что func1
вызывает func2
с некоторым вводом.
Теперь предположим, что func2
по какой-то причине не работает.
Ваше предложение - обработать сбой в func2
, а затем вернуться к func1
.
Как func1
"знает", какая ошибка (если есть) произошла в func2
и как исходить из этой точки?
Первое решение, которое приходит на ум, - это код func2
, возвращаемый func2
, где обычно нулевое значение будет представлять "ОК", а каждое из других (ненулевых) значений будет представлять собой определенную ошибку, которая произошла.
Проблема с этим механизмом заключается в том, что он ограничивает вашу гибкость при добавлении/обработке новых кодов ошибок.
С механизмом исключения у вас есть общий объект Exception
, который может быть расширен до любого конкретного типа исключения. В некотором роде он похож на код ошибки, но может содержать больше информации (например, строку сообщения об ошибке).
Вы все же можете спорить, конечно, "ну, что за try/catch
?" Почему бы просто не вернуть этот объект? ".
К счастью, на этот вопрос уже был дан ответ здесь очень подробно:
В C++ каковы преимущества использования исключений и try/catch вместо того, чтобы просто вернуть код ошибки?
В общем, существуют два основных преимущества для исключений по кодам ошибок, оба из которых - разные аспекты правильного кодирования:
-
За исключением этого, программист должен либо обработать его, либо выбросить "вверх", тогда как с кодом ошибки программист может ошибочно проигнорировать его.
-
С механизмом исключения вы можете написать свой код намного "чище" и иметь все, что "автоматически обрабатывается", с кодами ошибок, которые вы обязаны реализовать "утомительный" switch/case
, возможно, в каждой функции "вверх по стопку вызовов",,
Ответ 2
Исключения представляют собой более объектно-ориентированный подход к обработке исключительных потоков выполнения, чем коды возврата. Недостатком кодов возврата является то, что вам нужно придумать "специальные" значения, чтобы указать разные типы исключительных результатов, например:
public double calculatePercentage(int a, int b) {
if (b == 0) {
return -1;
}
else {
return 100.0 * (a / b);
}
}
Вышеуказанный метод использует код возврата -1 для указания отказа (поскольку он не может делить на ноль). Это будет работать, но ваш код вызова должен знать об этом соглашении, например, это может произойти:
public double addPercentages(int a, int b, int c, int d) {
double percentage1 = calculatePercentage(a, b);
double percentage2 = calculatePercentage(c, c);
return percentage1 + percentage2;
}
Над кодом выглядит на первый взгляд. Но когда b или d равны нулю, результат будет неожиданным. calculatePercentage вернет -1 и добавит его к другому проценту, который, скорее всего, неверен. Программист, который написал addPercentages, не знает, что в этом коде есть ошибка, пока он не проверит его, и даже тогда, только если он действительно проверяет достоверность результатов.
С исключениями вы можете сделать это:
public double calculatePercentage(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Second argument cannot be zero");
}
else {
return 100.0 * (a / b);
}
}
Код, вызывающий этот метод, будет компилироваться без обработки исключений, но он остановится при запуске с неправильными значениями. Это часто является предпочтительным способом, поскольку он оставляет его программисту, если и где обрабатывать исключения.
Если вы хотите заставить программиста обработать это исключение, вы должны использовать проверенное исключение, например:
public double calculatePercentage(int a, int b) throws MyCheckedCalculationException {
if (b == 0) {
throw new MyCheckedCalculationException("Second argument cannot be zero");
}
else {
return 100.0 * (a / b);
}
}
Обратите внимание, что calculatePercentage должен объявлять исключение в своей сигнатуре метода. Проверенные исключения должны быть объявлены таким образом, и вызывающий код должен либо их поймать, либо объявить в своей собственной сигнатуре метода.
Я думаю, что многие разработчики Java в настоящее время согласны с тем, что проверенные исключения являются битовыми инвазивными, поэтому большинство API в последнее время тяготеют к использованию исключенных исключений.
Вышеописанное исключение можно определить следующим образом:
public class MyCheckedCalculationException extends Exception {
public MyCalculationException(String message) {
super(message);
}
}
Создание настраиваемого типа исключения, например, имеет смысл, если вы разрабатываете компонент с несколькими классами и методами, которые используются несколькими другими компонентами, и вы хотите, чтобы ваш API (включая обработку исключений) был очень ясным.
(см. иерархию классов Throwable)
Ответ 3
Предположим, что вам нужно написать некоторый код для некоторого объекта, который состоит из n разных ресурсов (n> 3), которые должны быть выделены в конструкторе и освобождены внутри деструктора. Пусть даже говорят, что некоторые из этих ресурсов зависят друг от друга. Например, чтобы создать карту памяти какого-либо файла, сначала нужно было бы успешно открыть файл, а затем выполнить функцию ОС для сопоставления памяти. Без обработки исключений вы не сможете использовать конструктор для распределения этих ресурсов, но вы, вероятно, будете использовать двухэтапную инициализацию. Вам придется позаботиться о порядке строительства и разрушения самостоятельно, так как вы больше не используете конструктора. Без обработки исключений вы не сможете вернуть богатую информацию об ошибках вызывающему абоненту - вот почему в бесплатном программном обеспечении для исключения обычно требуется отладчик и отладка исполняемого файла, чтобы определить, почему неожиданно возникает некоторая сложная часть программного обеспечения. Это снова предполагает, что не каждая библиотека может просто сбрасывать эту информацию об ошибках в stderr. stderr в некоторых случаях недоступен, что, в свою очередь, делает весь код, который использует stderr для сообщения об ошибках, непригодным для использования. Используя C++ Exception Handling, вы просто привязываете классы, переносящие соответствующие системные вызовы в отношения базового или членского класса, и компилятор будет заботиться о порядке построения и уничтожения и только вызывать деструкторы для неработающих конструкторов.