Зачем нам нужна обработка исключений?

Я могу проверить вход и если это недопустимый ввод от пользователя, я могу использовать простое условие "if", которое печатает "вход недействителен, пожалуйста, заново введите" (в случае наличия недопустимого ввода).

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

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

Ответ 1

Предположим, что func1 вызывает func2 с некоторым вводом.

Теперь предположим, что func2 по какой-то причине не работает.

Ваше предложение - обработать сбой в func2, а затем вернуться к func1.

Как func1 "знает", какая ошибка (если есть) произошла в func2 и как исходить из этой точки?

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

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

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

Вы все же можете спорить, конечно, "ну, что за try/catch?" Почему бы просто не вернуть этот объект? ".

К счастью, на этот вопрос уже был дан ответ здесь очень подробно:

В C++ каковы преимущества использования исключений и try/catch вместо того, чтобы просто вернуть код ошибки?

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

  1. За исключением этого, программист должен либо обработать его, либо выбросить "вверх", тогда как с кодом ошибки программист может ошибочно проигнорировать его.

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