Обработка ошибок способы обработки ошибок

Обработка ошибок и исключения

Содержание

Методы обработки ошибок [ править ]

1. Не обрабатывать.

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

При вызове метода необходимо проверить возвращаемое значение:

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

3.Использовать флаг ошибки: при возникновении ошибки устанавливать флаг в соответствующее значение:

Минусы такого подхода аналогичны минусам использования кодов возврата.

4.Можно вызвать метод обработки ошибки и возвращать то, что вернет этот метод.

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

5.В случае ошибки просто закрыть программу.

Это приведет к потере данных, также невозможно понять, в каком месте возникла ошибка.

Исключения [ править ]

В Java возможна обработка ошибок с помощью исключений:

Проверять b на равенство нулю уже нет необходимости, так как при делении на ноль метод бросит непроверяемое исключение ArithmeticException .

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

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

Таким образом, механизм обработки исключений содержит следующие операции:

  1. Создание объекта-исключения.
  2. Заполнение stack trace’а этого исключения.
  3. Stack unwinding (раскрутка стека) в поисках нужного обработчика.

Классификация исключений [ править ]

Класс Java Throwable описывает все, что может быть брошено как исключение. Наследеники Throwable — Exception и Error — основные типы исключений. Также RuntimeException , унаследованный от Exception , является существенным классом.

Проверяемые исключения [ править ]

Наследники класса Exception (кроме наслеников RuntimeException ) являются проверяемыми исключениями(checked exception). Как правило, это ошибки, возникшие по вине внешних обстоятельств или пользователя приложения – неправильно указали имя файла, например. Эти исключения должны обрабатываться в ходе работы программы, поэтому компилятор проверяет наличие обработчика или явного описания тех типов исключений, которые могут быть сгенерированы некоторым методом.

Все исключения, кроме классов Error и RuntimeException и их наследников, являются проверяемыми.

Error [ править ]

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

RuntimeException [ править ]

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

Обработка исключений [ править ]

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

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

Возможна ситуация, когда одно исключение становится причиной другого. Для этого существует механизм exception chaining. Практически у каждого класса исключения есть конструктор, принимающий в качестве параметра Throwable – причину исключительной ситуации. Если же такого конструктора нет, то у Throwable есть метод initCause(Throwable) , который можно вызвать один раз, и передать ему исключение-причину.

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

try-catch-finally [ править ]

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

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

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

Код из блока finally выполнится в любом случае: при нормальном выходе из try , после обработки исключения или при выходе по команде return .

NB: Если JVM выйдет во время выполнения кода из try или catch , то finally -блок может не выполниться. Также, например, если поток выполняющий try или catch код остановлен, то блок finally может не выполниться, даже если приложение продолжает работать.

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

После того, как было брошено первое исключение — new Exception(«a») — будет выполнен блок finally , в котором будет брошено исключение new IOException(«b») , именно оно будет поймано и обработано. Результатом его выполнения будет вывод в консоль b . Исходное исключение теряется.

Обработка исключений, вызвавших завершение потока [ править ]

При использовании нескольких потоков бывают ситуации, когда поток завершается из-за исключения. Для того, чтобы определить с каким именно, начиная с версии Java 5 существует интерфейс Thread.UncaughtExceptionHandler . Его реализацию можно установить нужному потоку с помощью метода setUncaughtExceptionHandler . Можно также установить обработчик по умолчанию с помощью статического метода Thread.setDefaultUncaughtExceptionHandler .

Интерфейс Thread.UncaughtExceptionHandler имеет единственный метод uncaughtException(Thread t, Throwable e) , в который передается экземпляр потока, завершившегося исключением, и экземпляр самого исключения. Когда поток завершается из-за непойманного исключения, JVM запрашивает у потока UncaughtExceptionHandler , используя метод Thread.getUncaughtExceptionHandler() , и вызвает метод обработчика – uncaughtException(Thread t, Throwable e) . Все исключения, брошенные этим методом, игнорируются JVM.

Информация об исключениях [ править ]

  • getMessage() . Этот метод возвращает строку, которая была первым параметром при создании исключения;
  • getCause() возвращает исключение, которое стало причиной текущего исключения;
  • printStackTrace() печатает stack trace, который содержит информацию, с помощью которой можно определить причину исключения и место, где оно было брошено.

Все методы выводятся в обратном порядке вызовов. В примере исключение IllegalStateException было брошено в методе getBookIds , который был вызван в main . «Caused by» означает, что исключение NullPointerException является причиной IllegalStateException .

Разработка исключений [ править ]

Чтобы определить собственное проверяемое исключение, необходимо создать наследника класса java.lang.Exception . Желательно, чтобы у исключения был конструкор, которому можно передать сообщение:

Исключения в Java7 [ править ]

  • обработка нескольких типов исключений в одном catch -блоке:

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

Байт-код, сгенерированный компиляцией такого catch -блока будет короче, чем код нескольких catch -блоков.

  • Try с ресурсами позволяет прямо в try -блоке объявлять необходимые ресурсы, которые по завершению блока будут корректно закрыты (с помощью метода close() ). Любой объект реализующий java.lang.AutoCloseable может быть использован как ресурс.

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

Можно объявлять несколько ресурсов, разделяя их точкой с запятой:

Во время закрытия ресурсов тоже может быть брошено исключение. В try-with-resources добавленна возможность хранения «подавленных» исключений, и брошенное try -блоком исключение имеет больший приоритет, чем исключения получившиеся во время закрытия. Получить последние можно вызовом метода getSuppressed() от исключения брошенного try -блоком.

  • Перебрасывание исключений с улучшенной проверкой соответствия типов.

Компилятор Java SE 7 тщательнее анализирует перебрасываемые исключения. Рассмотрим следующий пример:

В примере try -блок может бросить либо FirstException , либо SecondException . В версиях до Java SE 7 невозможно указать эти исключения в декларации метода, потому что catch -блок перебрасывает исключение ex , тип которого — Exception .

В Java SE 7 вы можете указать, что метод rethrowException бросает только FirstException и SecondException . Компилятор определит, что исключение Exception ex могло возникнуть только в try -блоке, в котором может быть брошено FirstException или SecondException . Даже если тип параметра catch — Exception , компилятор определит, что это экземпляр либо FirstException , либо SecondException :

Если FirstException и SecondException не являются наследниками Exception , то необходимо указать и Exception в объявлении метода.

Примеры исключений [ править ]

  • любая операция может бросить VirtualMachineError . Как правило это происходит в результате системных сбоев.
  • OutOfMemoryError . Приложение может бросить это исключение, если, например, не хватает места в куче, или не хватает памяти для того, чтобы создать стек нового потока.
  • IllegalArgumentException используется для того, чтобы избежать передачи некорректных значений аргументов. Например:
  • IllegalStateException возникает в результате некорректного состояния объекта. Например, использование объекта перед тем как он будет инициализирован.

Гарантии безопасности [ править ]

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

  • Отсутствие гарантий (no exceptional safety). Если было брошено исключение, то не гарантируется, что все ресурсы будут корректно закрыты и что объекты, методы которых бросили исключения, могут в дальнейшем использоваться. Пользователю придется пересоздавать все необходимые объекты и он не может быть уверен в том, что может переиспозовать те же самые ресурсы.
  • Отсутствие утечек (no-leak guarantee). Объект, даже если какой-нибудь его метод бросает исключение, освобождает все ресурсы или предоставляет способ сделать это.
  • Слабые гарантии (weak exceptional safety). Если объект бросил исключение, то он находится в корректном состоянии, и все инварианты сохранены. Рассмотрим пример:

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

  • Сильные гарантии (strong exceptional safety). Если при выполнении операции возникает исключение, то это не должно оказать какого-либо влияния на состояние приложения. Состояние объектов должно быть таким же как и до вызовов методов.
  • Гарантия отсутствия исключений (no throw guarantee). Ни при каких обстоятельствах метод не должен генерировать исключения. В Java это невозможно, например, из-за того, что VirtualMachineError может произойти в любом месте, и это никак не зависит от кода. Кроме того, эту гарантию практически невозможно обеспечить в общем случае.

Источник

Размышления о способах обработки ошибок

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

Код в статье приводится на scala, однако рассматриваемый подход может быть реализован на многих других языках (c++ с помощью макросов, java с помощью JetBrains MPS и т.д.). Наиболее близким аналогом рассматриваемого подхода является способ обработки ошибок в haskell.

При проектировании функции способ работы с ошибками внутри неё зависит от предполагаемого способа использования в случае ошибки. Можно выделить два основных способа:

  • Ошибка ведет к некорректному состоянию программы и мы должны максимально быстро и подробно сообщить о ней пользователю. Системы с таким способом обработки ошибок обладают свойством fail-fast (FF).
  • Ошибка является стандартной ситуацией и будет обработана в автоматическом режиме без участия пользователя. Подобные системы являются fault-tolerant (FT)

Далее я буду использовать два сокращения: функция рассчитанная на то, что внешний код будет работать с ней в режиме FT — FT-функция и аналогично FF-функция.

Зачем вообще выделять несколько способов работы с ошибками, а не использовать какой-то один для всех случаев? Дело в том, что функции рассчитанные на FF применяемые в режиме FT, как правило, будут приводить к слишком медленной работе всей системы. В то время как функции рассчитанные на FT, используемые в режиме FF не будут предоставлять достаточно информации для быстрого поиска ошибки пользователем.

Множество примеров несоответствия целей и способов использования порождает стандартная функция из JDK Integer.parseInt, которая явно проектировалась из расчета на FF. Например если вы в цикле читаете строки из потока и обрабатываете только числа, а остальные пропускаете. В этом случае try < Integer.parseInt(. ) >catch <. >может замедлить ваш код в несколько раз. Более подробное объяснение и тесты можно посмотреть здесь nadeausoftware.com/articles/2009/08/java_tip_how_parse_integers_quickly.

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

Если вы пишете обычное бизнес- или веб-приложение, т.е. такое, где основную роль играет внешний пользователь (а таких приложений в настоящий момент подавляющее большинство), а не систему управления марсоходом, то 99% вашего кода будет FF. Т.е. от пользователя будут требовать ввода корректных данных, а если же какие-то некорректные всё-таки проскочили, то падать с ошибкой и требовать от разработчика добавить больше проверок. Весь код, обрабатывающий ошибки, будет занимать

Источник

Читайте также:  Технологическая схема производства цемента по сухому способу производства
Оцените статью
Разные способы