Действительно ли ловить общие исключения - это плохо?

58

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

CA1031: не перехватывать общие типы исключений

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

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

Может быть, лучший пример:

public void Foo()
{
    // Some logic here.
    LogUtility.Log("some message");
}

public static void Log()
{
    try
    {
        // Actual logging here.
    }
    catch (Exception ex)
    {
        // Eat it. Logging failures shouldn't stop us from processing.
    }
}

Если вы здесь не поймете общее исключение, то вам придется перехватывать все возможные типы исключений. У Патрика есть хорошая точка зрения, с которой OutOfMemoryExceptionне следует разбираться. Так что, если я хочу игнорировать все исключения, кроме OutOfMemoryException?

Боб Хорн
источник
12
Как насчет OutOfMemoryException? Такой же код обработки, как и все остальное?
Патрик
@ Патрик Хороший вопрос. Я добавил новый пример в свой вопрос, чтобы охватить ваш вопрос.
Боб Хорн
1
Поймать общие исключения примерно так же плохо, как делать общие заявления о том, что вы все должны делать. Как правило, нет универсального подхода ко всему.
4
@ Патрик, который был бы OutOfMemoryError, который отделен от Exceptionдерева наследования по той же самой причине
Darkhogg
Как насчет клавиатурных исключений, таких как Ctrl-Alt-Del?
Mawg

Ответы:

33

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

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

На встречной стороне аргумента.

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

Мартин Йорк
источник
2
Concur. По крайней мере , теперь, когда обычное ведение журнала отключено, создайте какое-то условие, которое даст STDERR какой- то отладочный вывод, если ничего больше.
Шадур
6
Я проголосовал за вас обоих, но я думаю, что вы думаете как разработчики, а не пользователи. Пользователь, как правило, не заботится о том, происходит ли ведение журнала, пока приложение пригодно для использования.
Mawg
1
Интересно, что log4net явно не хочет останавливать приложение только из-за сбоя регистрации. И я думаю, что это зависит от того, что вы регистрируете; Аудит безопасности, конечно, убивает процесс. Но отладочные журналы? Не так важно.
Энди
1
@ Энди: Да все зависит от того, что вы делаете.
Мартин Йорк
2
Почему ты их не понимаешь? И не стоит ли стремиться к этому?
Мауг
31

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

Есть несколько типов исключений, которые вы можете обработать:

  • Неустранимые исключения : нехватка памяти, переполнение стека и т. Д. Некоторая сверхъестественная сила просто испортила вашу вселенную, и процесс уже умирает. Вы не можете улучшить ситуацию, поэтому просто сдавайтесь
  • Исключение вызвано тем, что в используемом вами коде есть ошибки : не пытайтесь их обрабатывать, а исправьте источник проблемы. Не используйте исключения для управления потоком
  • Исключительные ситуации : обрабатывать исключения только в этих случаях. Здесь мы можем включить: сетевой кабель отключен, интернет-соединение перестало работать, отсутствующие разрешения и т. Д.

Да, и как правило: если вы не знаете, что делать с исключением, если вы его поймаете, лучше просто быстро потерпеть неудачу (передайте исключение вызывающей стороне и дайте ему обработать это)

Виктор Хурдугачи
источник
1
Как насчет конкретного случая в вопросе? Если в коде регистрации есть ошибка, то, вероятно, можно игнорировать исключение, потому что на код, который действительно имеет значение, это не должно повлиять.
svick
4
Почему бы вместо этого не исправить ошибку в журнале регистрации?
Виктор Хурдугачи
3
Виктор, это наверное не ошибка. Если на диске, на котором живет журнал, не хватает места, это ошибка в коде журнала?
Carson63000
4
@ Carson63000 - ну да. Журналы не пишутся, поэтому: ошибка. Реализация фиксированных размеров вращающихся бревен.
детально
5
Это лучший ответ imo, но в нем отсутствует один важный аспект: безопасность . Есть некоторые исключения, которые происходят из-за того, что плохие парни пытаются поднять вашу программу. Если возникает исключение, которое вы не можете обработать, вы больше не можете доверять этому коду. Никто не любит слышать, что они написали код, который
впускает
15

У самой верхней внешней петли должен быть один из них, чтобы напечатать все, что он может, а затем умереть ужасной, насильственной и ШУМНОЙ смертью (поскольку этого не должно произойти, и кто-то должен услышать).

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


источник
1
Я согласен с этим в теории. Но как насчет моего примера регистрации выше? Что если вы просто хотите игнорировать исключения журналирования и продолжить? Нужно ли вам отлавливать каждое исключение, которое может быть выдано, и обрабатывать каждое из них, позволяя всплыть более серьезному исключению?
Боб Хорн
6
@BobHorn: два взгляда на это. Да, вы можете сказать, что ведение журнала не является особенно важным, и если оно терпит неудачу, то оно не должно останавливать ваше приложение. И это достаточно справедливо. Но что, если ваше приложение не может войти в систему в течение пяти месяцев, и вы не знали? Я видел, как это случилось. И тогда нам понадобились логи. Катастрофа. Я бы посоветовал сделать все возможное, чтобы перестать регистрировать ошибки, лучше, чем игнорировать его, когда это происходит. (Но вы не достигнете большого консенсуса по этому вопросу.)
pdr
@pdr В моем примере регистрации я обычно посылаю оповещение по электронной почте. Или у нас есть системы для мониторинга журналов, и если журнал не заполняется активно, наша служба поддержки получит предупреждение. Таким образом, мы узнали о проблеме журналирования другими способами.
Боб Хорн
@BobHorn ваш пример ведения журнала довольно надуманный - большинство известных мне каркасов ведения журналов идут очень долго, чтобы не выдавать никаких исключений, и не будут вызываться таким образом (поскольку это сделает бесполезным любое «утверждение журнала, произошедшее в строке X в файле Y») )
@pdr Я поднял вопрос в списке logback (Java) о том, как заставить каркас журналирования останавливать приложение, если конфигурирование логирования завершается неудачей, просто потому, что это ТАК важно, что у нас есть журналы, а любой тихий сбой недопустим. Хорошее решение еще не принято.
9

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

Райан Хейс
источник
5

Две возможности не являются взаимоисключающими.

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

try
{
    this.Foo();
}
catch (BarException ex)
{
    Console.WriteLine("Foo has barred!");
}
catch (BazException ex)
{
    Console.WriteLine("Foo has bazzed!");
}
catch (Exception ex)
{
    Console.WriteLine("Unknown exception on Foo!");
    throw;
}

Имейте в виду, что для того, чтобы поймать более конкретные исключения, вы должны поместить их в первую очередь.

Изменить: По причинам, указанным в комментариях, добавлен пересчет в последнем улове.

Rotem
источник
2
Подождите. Зачем вам когда-либо хотеть записать неизвестное исключение на консоль и продолжить? Если это исключение, которое вы даже не знали, может быть вызвано кодом, вы, вероятно, говорите о сбое системы. Продолжать в этом сценарии, вероятно, действительно плохая идея.
фунтовые
1
@pdr Конечно, вы понимаете, что это надуманный пример. Задача состояла в том, чтобы продемонстрировать как конкретные, так и общие исключения, а не то, как их обрабатывать.
Rotem
@Rotem Я думаю, у вас есть хороший ответ здесь. Но у pdr есть смысл. Код, как есть, делает его похожим на перехват и продолжение - это нормально. Некоторые новички, видя этот пример, могут подумать, что это хороший подход.
Боб Хорн
Придумано или нет, это делает ваш ответ неверным. Как только вы начинаете ловить исключения, такие как Out Of Memory и Disk Full, и просто глотаете их, вы доказываете, почему перехват общих исключений на самом деле плох.
фунтовые
1
Если вы ловите общее исключение только для его регистрации, то вам следует сбросить его после регистрации.
Виктор Хурдугач
5

Недавно я размышлял над тем же, и мой предварительный вывод заключается в том, что простой вопрос возникает из-за того, что иерархия исключений .NET серьезно испорчена.

Возьмем, к примеру, простой, ArgumentNullExceptionкоторый может быть разумным кандидатом на исключение, которое вы не хотите перехватывать, потому что оно имеет тенденцию указывать на ошибку в коде, а не на допустимую ошибку времени выполнения. О да. То же самое NullReferenceException, за исключением того, что NullReferenceExceptionоно получено SystemExceptionнапрямую, поэтому нет корзины, в которую вы можете поместить все «логические ошибки», чтобы поймать (или не поймать).

Кроме того, в IMNSHO есть главный недостаток - SEHExceptionполучить (через ExternalException) SystemExceptionи, таким образом, сделать его «нормальным».SystemException Когда вы получаетеSEHException, вы хотите написать дамп и завершить работу так быстро, как можете - и начиная с .NET 4, по крайней мере, некоторые Исключения SEH считаются поврежденными состояниями , которые не будут обнаружены. Хорошая вещь, и факт, который делаетCA1031правило еще более бесполезным, потому что теперь ваш ленивыйcatch (Exception)не поймает эти худшие типы в любом случае.

Затем кажется, что другие компоненты Framework довольно непоследовательно извлекаются либо Exceptionнапрямую, либо через SystemException, пытаясь сгруппировать предложения catch по какому-либо вопросу, связанному с серьезностью.

Мистер Липперт C#, известный как Vexing Exception , предлагает вам полезную классификацию исключений: можно утверждать, что вы хотите поймать только «экзогенные» исключения, кроме ... C#языка и дизайна .NET рамочные исключения делают невозможным «ловить только экзогенных» любым лаконичным образом. (и, например, OutOfMemoryExceptionможет очень хорошо быть совершенно нормально неустранимая ошибка для API , который должен выделять буферы, которые как - то большой)

Суть в том, что то, как C#работают блоки catch и как устроена иерархия исключений Framework, правило CA1031выходит за рамки абсолютно бесполезного . Он притворяется, что помогает с основной проблемой «не глотать исключения», но проглатывание исключений не имеет ничего общего с тем, что вы ловите, а с тем, что вы затем делаете:

Есть как минимум 4 способа законно обработать пойманное Exception, и CA1031только кажется, что поверхностно обрабатывает один из них (а именно случай повторного броска).


Как примечание, есть функция C # 6 под названием « Фильтры исключений», которая снова станет CA1031немного более корректной, поскольку тогда вы сможете правильно, правильно, правильно фильтровать исключения, которые вы хотите перехватить, и меньше причин писать нефильтрованные catch (Exception).

Мартин Ба
источник
1
Основная проблема OutOfMemoryExceptionзаключается в том, что нет хорошего способа, которым код может быть уверен, что он «только» указывает на сбой определенного распределения, которое было подготовлено к сбою. Вполне возможно, что OutOfMemoryExceptionвозникло еще одно более серьезное исключение, и во время разматывания стека произошло другое исключение. Java, возможно, опоздала на вечеринку со своими «try-with-resources», но она обрабатывает исключения во время разматывания стека немного лучше, чем .NET.
суперкат
1
@supercat - достаточно верно, но это в значительной степени верно для любого другого общего исключения системы, когда в каком-либо блоке finally все идет плохо.
Мартин Ба
1
Это правда, но можно (и, как правило, следует) защищать finallyблоки от исключений, которые можно реально ожидать, чтобы они происходили таким образом, что оба исходных и новых исключения регистрируются. К сожалению, для этого часто требуется выделение памяти, что может вызвать собственные сбои.
суперкат
4

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

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

Посмотрите на этот так ответ для большего количества чтения.

Том Сквайрс
источник
1
Эта ситуация (когда разработчик должен быть Pokemon Trainer) возникает, когда вы создаете целое программное обеспечение самостоятельно: вы сделали слои, соединения с базой данных и пользовательский интерфейс пользователя. Ваше приложение должно восстанавливаться после таких ситуаций, как потеря связи, удаление пользовательской папки, повреждение данных и т. Д. Конечным пользователям не нравятся приложения, которые аварийно завершают работу. Им нравятся приложения, которые могут работать на горячем взрывающемся компьютере!
Broken_Window
Я не думал об этом до сих пор, но в Pokemon, как правило, предполагается, что «поймать их всех» вы хотите точно один из каждого, и вам нужно обрабатывать его специально, что является противоположностью обычного использования этой фразы среди программистов.
Маг
2
@Magus: С помощью такого метода LoadDocument(), по сути, невозможно идентифицировать все вещи, которые могут пойти не так, но 99% исключений, которые могут быть сгенерированы, будут просто означать «Не удалось интерпретировать содержимое файла с данным именем как документ; разберись с ним ". Если кто-то пытается открыть что-то, что не является допустимым файлом документа, это не должно привести к сбою приложения и уничтожению любых других открытых документов. Обработка ошибок покемонов в таких случаях безобразна, но я не знаю хороших альтернатив.
суперкат
@supercat: Я просто высказал лингвистическую мысль. Тем не менее, я не думаю, что недопустимое содержимое файла звучит как нечто, что должно вызывать более одного вида исключений.
Маг
1
@Magus: он может выдавать всевозможные исключения - вот в чем проблема. Зачастую очень трудно предвидеть все виды исключений, которые могут быть вызваны вследствие неправильных данных в файле. Если вы не используете обработку PokeMon, вы рискуете умереть от приложения, потому что, например, файл, который он загружал, содержал исключительно длинную строку цифр в месте, которое требовало 32-разрядное целое число в десятичном формате, и числовое переполнение происходило в логика разбора.
суперкат
1

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

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

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

Питер Б
источник
0

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

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

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

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

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

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

Так что, если я хочу игнорировать все исключения, кроме OutOfMemoryException?

ИМХО, это неправильный способ думать об обработке исключений. Вы должны работать с белыми списками (перехватывать типы исключений A и B), а не с черными списками (перехватывать все исключения, кроме X).

Джон
источник
Часто можно указать действие, которое должно быть выполнено для всех исключений, которые указывают на то, что попытка операции завершилась неудачно без каких-либо побочных эффектов или неблагоприятного влияния на состояние системы. Например, если пользователь выбирает файл документа для загрузки, программа передает свое имя методу «создание объекта документа с данными из файла на диске», и этот метод завершается ошибкой по какой-то конкретной причине, которую приложение не ожидало (но которое соответствует указанным выше критериям), правильное поведение должно отображать сообщение «Невозможно загрузить документ», а не сбой приложения.
суперкат
К сожалению, нет стандартных средств, с помощью которых исключение может указывать на самую важную вещь, которую клиентский код хотел бы знать - подразумевает ли это что-либо плохое в состоянии системы, помимо того, что подразумевалось бы невозможностью выполнить запрошенную операцию. Таким образом, даже несмотря на то, что ситуации, когда все исключения должны обрабатываться, являются редкими, существует множество ситуаций, в которых многие исключения должны обрабатываться одинаково, и нет критерия, который удовлетворял бы всем таким исключениям, также был бы удовлетворен некоторыми редкими, которые должны быть обработаны по-другому.
суперкат
0

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

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

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

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

Некоторые проблемы (например, переполнение диска) могут также привести к сбою других частей приложения - но этот сбой не регистрируется сейчас, так что вы никогда не узнаете!

JacquesB
источник
0

Я хотел бы рассмотреть это с логической точки зрения, а не с технической точки зрения,

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

Ну, кто-то должен был бы справиться с этим. Это идея. Авторы библиотечного кода будут осторожны с добавлением новых типов исключений, хотя, поскольку это может привести к поломке клиентов, вы не должны сталкиваться с этим очень часто.

По сути, ваш вопрос: «Что, если мне все равно, что пошло не так? Мне действительно нужно пройти через испытание, чтобы выяснить, что это было?»

Вот часть красоты: нет, ты не.

«Итак, можно я просто посмотрю в другую сторону и сделаю так, чтобы всякая неприятная вещь всплыла под ковром автоматически и с этим покончено?»

Нет, это тоже не так.

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

«Но ... но ... тогда одно из тех других исключений, которые меня не волнуют, может привести к сбою моей задачи!»

Да. Но они всегда будут важнее, чем локальные. Например, пожарная сигнализация или босс, говорящий вам прекратить то, что вы делаете, и поднять более неотложное задание, которое появилось.

Мартин Маат
источник
-3

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

Вы не должны отлавливать общие исключения и пытаться продолжить выполнение.

ddyer
источник