Страницы

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

воскресенье, 24 ноября 2019 г.

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


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

UPD1:  


Когда делать RuntimeException, а когда просто Exception?  
Единственен ли способ генерации ошибки оператором throw? 
В каких лучше случаях создать, допустим, MyException extends Exception, а затем MyOtherException extends MyException? Иными словами, как увидеть необходимость в иерархии ошибок?
Когда делать примерно так:

public void f() throws MyException {
    //некоторый код ...
    if (....){
        throw MyException(...);
    }
}

Какие еще есть способы генерации ошибок?
Когда правильнее переносить ошибку на уровень функции (... f() throws ...)?

    


Ответы

Ответ 1



Во-первых, как уже говорилось, используйте исключения. Создайте свою иерархию исключени и продумайте, какие из них сделать 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 Да, пожалуй, по моему мнению, хорошим примером неправильного выбора между Exceptio и RuntimeException является InterruptedException. Было бы гораздо лучше, если бы о был Runtime. В результате либо всюду приходится его ловить и игнорировать, либо везде пробрасывать.. продумывая кейз, который никогда не произойдёт. И эти загромождения сильно портят код.

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

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