Страницы

Поиск по вопросам

воскресенье, 30 сентября 2018 г.

Хороший стиль обработки ошибок

Правильная и хорошая обработка ошибок и их генерация — довольно важная часть в разработке ПО. Но как это делать правильно и красиво — советов мало, и они очень поверхностны. Объясните, пожалуйста, принципы хорошей обработки ошибок (желательно в Java) вместе с примерами.
UPD1:
Когда делать RuntimeException, а когда просто Exception? Единственен ли способ генерации ошибки оператором throw? В каких лучше случаях создать, допустим, MyException extends Exception, а затем MyOtherException extends MyException? Иными словами, как увидеть необходимость в иерархии ошибок? Когда делать примерно так:
public void f() throws MyException { //некоторый код ... if (....){ throw MyException(...); } } Какие еще есть способы генерации ошибок? Когда правильнее переносить ошибку на уровень функции (... f() throws ...)?


Ответ

Во-первых, как уже говорилось, используйте исключения. Создайте свою иерархию исключений и продумайте, какие из них сделать runtime, а какие нет. Никогда не используйте конструкции типа new Exception(...) или new RuntimeException. Вместо этого старайтесь создавать и кидать только адекватные ситуации исключения. Создание экземпляра Exception или RuntimeException — халтура и отписка вместо обработки ошибок.
Не забывайте проверять все параметры в конструкторах и, по возможности, в методах и своевременно кидать IllegalArgumentException. Пользуйтесь стандартными исключениями в соответствующих случаях. Используйте их по назначению и никогда не кидайте что попало.
Пользуйтесь try/catch/finally. Не забывайте уничтожать ресурсы.
Пользуйтесь логгерами, а не делайте e.printStackTrace. Никогда не делайте catch пустым, если только вы не уверены, что должны именно проигнорировать исключение.
При логгировании пользуйтесь по возможности полный метод log с уровнем логгирования, сообщением и исключением. Старайтесь в сообщении к логу описать, что именно упало и добавить какие-то сведения об условиях в блоке try. Это поможет выявить проблемы в боевых условиях. Старайтесь назначать адекватные уровни логгирования.
Настраивайте логгер. В некоторых случаях следует разделять логи для ошибок и варнингов. В противном случае сообщения об ошибках могут сротироваться и потеряться из-за менее важных сообщениях.
Подцепите что-то вроде аудита, если приложение серверное. Возможно стоит в особо фатальных случаях слать письмо админам.
Если это клиентское приложение на свинге, то можно отображать сообщение об ошибке с окне или в виде маленькой иконки в панели статуса.
UPD1
Когда делать RuntimeException, а когда просто Exception?
Это довольно сложный вопрос и трудно дать общие рекомендации. Но в общем случае следует руководствоваться фатальностью ошибки и её вероятности возникновения. Также следует учитывать возможное расстояние от места генерирования ошибки до места фактической обработки. Например, IOException нельзя просто никак не обработать и это верно. Если его не обработать, то могут застрять какие-то ресурсы. И обычно обработка такой ошибки находится сравнительно близко от места её возникновения. В то же время IllegalArgumentException можно не отлавливать... так как обычно это ошибки, связанные с неправильным использованием API, неверное конфигурацией или недостаточной валидацией данных от пользователя. И попытка ловить такие исключения всюду привела бы к краху: код стал бы просто нечитаем. Иными словами, требуется здравый смысл.
Единственен ли способ генерации ошибки оператором throw?
Нет, исключение не обязательно кидать. Например, в GWT (и не только) имеется интерфейс AsyncCallback, который имеет метод onError, принимающий исключение в качестве параметра. Соответствующий код не кидает исключение через throw, а просто вызывает callback-метод и передаёт туда исключение. А уж callback сам решит, что с этим исключением делать.
В каких лучше случаях создать, допустим, MyException extends Exception, а затем MyOtherException extends MyException? Иными словами, как увидеть необходимость в иерархии ошибок?
Очевидно в тех случаях, когда между исключениями могут быть какие-то «родственные» связи. Когда какие-то группы исключений имеют что-то общее.
Когда делать примерно так:
public void f() throws MyException { //некоторый код ... if (....){ throw MyException(...); } }
Всегда, когда if позволяет определить, что что-то пошло не по плану.
Какие еще есть способы генерации ошибок?
Собственно, assert, throwи просто вызов обработчика с параметром-исключением.
Когда правильнее переносить ошибку на уровень функции (... f() throws ...)?
Опять же лучший пример — IOException. Так следует делать всегда, когда есть ошибка, но вы точно знаете, что клиент (вызывающий ваш метод) может захотеть узнать об ошибке явно, а не по косвенным признакам. Так, например, если кто-то читает из сокета, то он хочет знать, что чтение невозможно и просто предвать операцию вместо того, чтобы каждый раз проверять, работает ли сокет или лепить бесконечные if-ы проверяя код ошибки после каждого метода read.
UPD
Да, пожалуй, по моему мнению, хорошим примером неправильного выбора между Exception и RuntimeException является InterruptedException. Было бы гораздо лучше, если бы он был Runtime. В результате либо всюду приходится его ловить и игнорировать, либо везде пробрасывать.. продумывая кейз, который никогда не произойдёт. И эти загромождения сильно портят код.

Комментариев нет:

Отправить комментарий