Страницы

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

пятница, 29 ноября 2019 г.

Как правильно использовать исключения

#c_sharp #исключения


Преимущественно всегда думал, что бывает не лишним на каждый тип исключения выводить
соответствующее сообщение об ошибке. Речь идет о ситуациях, когда ничего кроме сообщения
в блоке catch делать не зачем, т.е. можно было бы просто использовать общий вариант:

catch (Exception ex)
   {
       // <сообщение>
   }


а не 

catch (DivideByZeroException)
   {
       // <сообщение про одно>     
   }
catch (FormatException)
   {
       // <сообщение про другое>     
   }
catch (ArithmeticException)
   {
       // <сообщение про третье>     
   }
catch (Exception ex)
   {
       // <сообщение>
   }


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

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

Так как поступать с исключениями в таких ситуациях? Определять "на глаз" в каком
месте обязательно нужно уведомлять о возможных причинах ошибки, а в каком достаточно
общего сообщения? Или есть какие-то уже сформулированные критерии по этому поводу?
Или по сути вообще может без разницы, и все эти рассуждения не имеют значения?
    


Ответы

Ответ 1



Вопрос конечно очень холиварный =) но тем не менее, поделюсь своими представлениями, ни в коей мере не претендуя на абсолютную истину. Любая программа, работающая в реальных условиях и окружении с участием пользователя или без, способна генерировать массу исключений по разным причинам. Исключение составляют учебно-олимпиадные программы, которые работают в "идеальных" условиях, т.е. входные данные жестко регламентированы и ошибка пользователя исключена условиями задачи. У каждого исключения может быть несколько причин для появления: Ошибка пользователя при вводе данных тем или иным способом. Исключения возникшие по этой причине ловить необходимо, чтобы сообщить пользователю о его ошибке и дать возможность повторить ввод данных или некоторый набор действий для продолжения работы программы. Ошибка возникшая по вине среды окружения, например операционной системы или другой программы или библиотеки с которой вам приходится взаимодействовать. Такие исключения, в среднем, ловить противопоказано, т.к. единственное что можно сделать в таком случае, это попытаться корректно завершить работу, однако даже это может привести к непредсказуемым последствиям и порче данных пользователя, что вряд ли обрадует последнего. Однако, если вы уверены что ошибка, вызвавшая исключение, безвредна для данных или само исключение несет сугубо информативный характер, как, например, исключения при попытке установить сетевое соединение или при работе с файлами, то такое исключение может быть поймано и обработано. Ошибка возникшая по вине самой программы или ее части, например "любимый" всеми NullReferenceException. Исключения подобного рода необходимы на этапе отладки. Ловить их в коде никакого смысла нет, т.к. в процессе отладки, в идеале, все места кода, где могут возникнуть исключения по вине программы, должны быть исправлены. Как временная мера, можно и поймать, но тут есть большой риск что эта временная мера так и останется навсегда костылем в коде. В целом, исключение генерируется тогда, когда исполняемый код не знает что делать в возникшей ошибкой. Я для себя вывел 3 простых правила по обработке исключений: Если можно исправить код так, чтобы исключение не возникало - исправляем код. Если не знаешь что делать с ошибкой которая стала причиной исключения, не лови его и дай программе упасть. Если уверен что знаешь что делать с ошибкой, ловим, заносим в логи, исправляем ошибку и бежим дальше либо падаем, тут уж по обстоятельствам.

Ответ 2



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

Ответ 3



Судя по описанию в исходном вопросе, ваш вопрос скорее -- как правильно обрабатывать исключения. (Потому что есть совсем другой вопрос -- как правильно выбрасывать исключения.) Есть общие правила, о которых тут уже говорили (и всегда говорят) -- не надо ловить все исключения, кроме как на самом верху, а там записывать их (логировать) для последующего анализа. Это правило становится понятным, когда увидишь какие исключения вообще могут выпадать. Например, ловить Out of memory или Thread abort вообще нет никакого смысла. Помимо этого, возникает чисто прикладной вопрос -- а какие исключения ловить в конкретном случае. Вызываем вот такой метод, и что нам от него ожидать? Если речь идёт о библиотеке .NET Framework или какой-то известной сторонней библиотеке -- можно почитать справку по методу (XML docs), там должно быть описано чего стоит ожидать. Но в общем случае -- приходится делать серию пробных запусков с ожидаемыми сценариями падений, и ставить отлов тех исключений что выпали.

Ответ 4



Смысл исключений — развести точку возникновения ошибки и точку её обработки. Иначе нету никакой разницы между исключением и возвратом кода ошибки. Поэтому отлов исключений обычно производится не везде, а в ключевых точках программы. Запустили логическую операцию — она либо выполнилась и произвела результат, либо выбросила исключение. Обычно в точке, где ловится исключение, вы не знаете точный список всех типов исключений (а в ситуации с динамическим кодом, например, с DI, можете вообще быть не в состоянии узнать). Поэтому в ключевых точках обычно стоит ловить достаточно общие виды исключений. (Но, разумеется, не все возможные.) С другой стороны, имеет смысл организовывать модули так, чтобы исключения, которые выбрасывает модуль, имели либо стандартный тип (ArgumentException, FileFormatException и т. п.), или наследовались от немногих общих корней. В этой ситуации вы можете поймать довольно исключение, залогировать его (эта информация важна для разработчика), и продолжать дальше выполнение программы. Или не продолжать, если сорвалась критическая операция — решать вам.

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

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