Неужели так плохо поймать общее исключение?
При анализе некоторого унаследованного кода с помощью FXCop мне пришло в голову, действительно ли плохо отлавливать общую ошибку исключения в блоке try или вы должны искать конкретное исключение. Мысли на открытке, пожалуйста.
16 ответов
Очевидно, что это один из тех вопросов, где единственный реальный ответ - "это зависит".
Главное, от чего это зависит - где вы ловите исключение. В целом библиотеки должны быть более консервативными с перехватом исключений, тогда как на верхнем уровне вашей программы (например, в вашем основном методе или в верхней части метода действия в контроллере и т. Д.) Вы можете быть более либеральными в отношении того, что вы ловите.
Причина этого в том, что, например, вы не хотите перехватывать все исключения в библиотеке, потому что вы можете маскировать проблемы, которые не имеют ничего общего с вашей библиотекой, такие как "OutOfMemoryException", которые вы действительно предпочли бы всплывать, чтобы пользователь мог уведомление и т. д. С другой стороны, если вы говорите о перехвате исключений внутри метода main(), который перехватывает исключение, отображает его и затем завершает работу... ну, здесь, вероятно, безопасно отлавливать практически любое исключение.
Самым важным правилом перехвата всех исключений является то, что вы никогда не должны просто молча проглатывать все исключения... например, что-то вроде этого в Java:
try {
something();
} catch (Exception ex) {}
или это в Python:
try:
something()
except:
pass
Потому что это может быть одним из самых сложных вопросов для отслеживания.
Хорошее эмпирическое правило заключается в том, что вы должны ловить только те исключения, с которыми вы можете справиться. Если вы не можете обработать исключение полностью, тогда вы должны позволить этому всплеснуть тому, кто может.
Если вы не ведете логи и не очищаете код в интерфейсе своего приложения, то я думаю, что это неправильно - перехватывать все исключения.
Мое основное правило - ловить все ожидаемые исключения, а все остальное - ошибка.
Если вы все поймаете и продолжите, это все равно что наклеить липкую штукатурку на сигнальную лампу на приборной панели вашего автомобиля. Вы не можете видеть это больше, но это не значит, что все в порядке.
Да! (кроме как в верхней части вашего приложения)
Улавливая исключение и позволяя продолжить выполнение кода, вы заявляете, что знаете, как справиться и обойти или исправить конкретную проблему. Вы заявляете, что это исправимая ситуация. Catching Exception или SystemException означает, что вы будете обнаруживать такие проблемы, как ошибки ввода-вывода, сетевые ошибки, ошибки нехватки памяти, ошибки отсутствующего кода, разыменование нулевого указателя и тому подобное. Это неправда, что вы можете справиться с этим.
В хорошо организованном приложении эти неустранимые проблемы должны обрабатываться в стеке.
Кроме того, по мере развития кода вы не хотите, чтобы ваша функция перехватывала новое исключение, которое будет добавлено в будущем к вызываемому методу.
На мой взгляд, вы должны отлавливать все ожидаемые исключения, но это правило относится ко всему, кроме логики вашего интерфейса. На всем пути вниз по стеку вызовов вы, вероятно, должны создать способ для перехвата всех исключений, вести некоторую регистрацию / предоставлять обратную связь с пользователем и, если необходимо и возможно, корректно завершать работу.
Нет ничего хуже, чем приложение, аварийно завершающее работу с недружелюбной пользовательской трассировкой стека, выведенной на экран. Это не только дает (возможно, нежелательное) понимание вашего кода, но также сбивает с толку вашего конечного пользователя, а иногда даже отпугивает его для конкурирующего приложения.
Там было много философских дискуссий (больше похоже на аргументы) по этому вопросу. Лично я считаю, что худшее, что вы можете сделать, это проглотить исключения. Следующее худшее - это позволить исключению всплыть на поверхность, где пользователь получит неприятный экран, полный технической паузы.
Что ж, я не вижу никакой разницы между перехватом общего или конкретного исключения, за исключением того, что при наличии нескольких блоков перехвата вы можете реагировать по-разному в зависимости от того, что является исключением.
В заключение вы поймаете оба IOException
а также NullPointerException
с общим Exception
, но способ, которым ваша программа должна реагировать, вероятно, отличается.
Дело двоякое, я думаю.
Во-первых, если вы не знаете, какое исключение произошло, как вы можете надеяться на восстановление после него. Если вы ожидаете, что пользователь может ввести неправильное имя файла, вы можете ожидать FileNotFoundException и попросить пользователя повторить попытку. Если этот же код сгенерировал исключение NullReferenceException, и вы просто попросили пользователя повторить попытку, он не узнает, что произошло.
Во-вторых, руководящие принципы FxCop сосредоточены на коде библиотеки / фреймворка - не все их правила предназначены для применения на веб-сайтах EXE или ASP.Net. Поэтому хорошо иметь глобальный обработчик исключений, который будет регистрировать все исключения и красиво выходить из приложения.
Проблема с перехватом всех исключений заключается в том, что вы можете ловить те, которые вы не ожидаете, или те, которые вы не должны ловить. Дело в том, что исключение любого рода указывает на то, что что-то пошло не так, и вы должны разобраться с этим, прежде чем продолжить, иначе вы можете столкнуться с проблемами целостности данных и другими ошибками, которые не так легко отследить.
Чтобы привести один пример, в одном проекте я реализовал тип исключения CriticalException. Это указывает на состояние ошибки, которое требует вмешательства разработчиков и / или административного персонала, в противном случае клиенты получают неверный счет или могут возникнуть другие проблемы с целостностью данных. Это также может быть использовано в других подобных случаях, когда просто регистрировать исключение недостаточно, и необходимо отправить оповещение по электронной почте.
Другой разработчик, который неправильно понимал концепцию исключений, затем обернул некоторый код, который потенциально может вызвать это исключение, в общий блок try... catch, который отбросил все исключения. К счастью, я заметил это, но это могло привести к серьезным проблемам, особенно с учетом того, что "очень необычный" угловой случай, который он должен был поймать, оказался гораздо более распространенным, чем я ожидал.
Поэтому в целом перехват общих исключений является плохим, если вы не уверены на 100%, что точно знаете , какие исключения будут выбрасываться и при каких обстоятельствах. Если вы сомневаетесь, пусть они всплывают в обработчике исключений верхнего уровня.
Подобное правило здесь никогда не бросает исключения типа System.Exception. Вы (или другой разработчик), возможно, захотите перехватить ваше конкретное исключение выше стека вызовов, позволяя другим проходить через него.
(Однако есть один момент, на который следует обратить внимание. В.NET 2.0, если поток встречает какие-либо неперехваченные исключения, он выгружает весь домен приложения. Так что вы должны заключить основное тело потока в общий блок try... catch и pass любые исключения, обнаруженные в вашем глобальном коде обработки исключений.)
Я хотел бы сыграть адвоката дьявола за то, что он поймал Исключение, зарегистрировал его и перебросил. Это может быть необходимо, если, например, вы где-то находитесь в коде и происходит непредвиденное исключение, вы можете его перехватить, записать значимую информацию о состоянии, которая не будет доступна в простой трассировке стека, и затем перебросить ее на верхние уровни, чтобы иметь дело с.
Есть два совершенно разных варианта использования. Первый - это то, о чем большинство людей думает, ставя попытку / поймать какую-то операцию, которая требует проверенного исключения. Это не должно быть всеобъемлющим.
Второе, однако, состоит в том, чтобы остановить вашу программу, если она может продолжаться. Эти случаи:
- Вершина всех потоков (по умолчанию исключения исчезают без следа!)
- Внутри основного цикла обработки, который вы ожидаете никогда не завершать
- Внутри цикла обработки списка объектов, где один сбой не должен останавливать другие
- Начало "основного" потока. Здесь вы можете управлять сбоем, например выгрузить немного данных в стандартный вывод, когда у вас закончится память.
- Если у вас есть "Runner", который запускает код (например, если кто-то добавляет слушателя к вам и вы вызываете слушателя), то когда вы запускаете код, вы должны перехватить Exception для регистрации проблемы и позволить вам продолжать уведомлять других слушателей.
В этих случаях вы ВСЕГДА хотите перехватить Exception (может быть, даже Throwable иногда), чтобы перехватывать программные / непредвиденные ошибки, регистрировать их и продолжать.
Большую часть времени ловить общее исключение не нужно. Конечно, бывают ситуации, когда у вас нет выбора, но в этом случае я думаю, что лучше проверить, почему вы должны его поймать. Может быть, что-то не так в вашем дизайне.
Непопулярное мнение: не совсем.
Поймайте все ошибки, которые вы можете существенно исправить. Иногда это все из них.
По моему опыту, гораздо важнее, откуда возникло исключение, чем то, которое фактически выдается. Если вы храните свои исключения в узком кругу, вы обычно не будете глотать что-либо, что в противном случае было бы полезно. Большая часть информации, закодированной в типе ошибки, является вспомогательной информацией, так что вы все равно в конечном итоге эффективно перехватываете их все (но теперь вам нужно искать документы API, чтобы получить полный набор возможных исключений).
Имейте в виду, что некоторые исключения должны всплывать почти во всех случаях, например, в Python. KeyboardInterrupt
а также SystemExit
, К счастью для Python, они хранятся в отдельной ветви иерархии исключений, поэтому вы можете позволить им всплыть, перехватывая Exception
, Хорошо продуманная иерархия исключений делает этот тип вещей действительно простым.
Основное время обнаружения общих исключений будет вызывать серьезные проблемы, когда имеешь дело с ресурсами, которые необходимо очистить (возможно, в finally
пункт), так как универсальный обработчик может легко пропустить подобные вещи. К счастью, это не проблема для языков с defer
, конструкции как у Питона with
или RAII в C++ и Rust.
Особенность перехвата конкретного исключения заключается в том, что код может выдать другое исключение, и блок перехвата не будет выполнен.
Пример: вы ловите IOException, но получаете StringOutOfBoundException. Итак, это зависит. Единственное, что нужно убедиться, — это отловить все возможные неудачные случаи внутри оператора try{}. Множественные перехваты с последним исключением. Это действительно зависит.
Я думаю, что хороший совет - перехватывать только определенные исключения внутри фреймворка (чтобы хост-приложение могло справляться с крайними случаями, такими как заполнение диска и т. Д.), Но я не понимаю, почему мы не должны быть в состоянии перехватить все исключения из нашего кода приложения. Проще говоря, бывают ситуации, когда вы не хотите, чтобы приложение зависало, что бы ни случилось.
Для моего класса IabManager, который я использовал для биллинга в приложении (из онлайн-примера TrivialDrive), я иногда замечал, что сталкиваюсь с множеством исключений. Дошло до того, что было непредсказуемо.
Я понял, что до тех пор, пока я перестану пытаться потреблять продукт в приложении после того, как произойдет одно исключение, и именно там будет происходить большинство исключений (при потреблении, а не при покупке), я буду в безопасности.
Я просто изменил все исключения на общее исключение, и теперь мне не нужно беспокоиться о каких-либо других случайных непредсказуемых исключениях.
До:
catch (final RemoteException exc)
{
exc.printStackTrace();
}
catch (final IntentSender.SendIntentException exc)
{
exc.printStackTrace();
}
catch (final IabHelper.IabAsyncInProgressException exc)
{
exc.printStackTrace();
}
catch (final NullPointerException exc)
{
exc.printStackTrace();
}
catch (final IllegalStateException exc)
{
exc.printStackTrace();
}
После:
catch (final Exception exc)
{
exc.printStackTrace();
}
Улавливая общее исключение, я чувствую, что держу палку динамита внутри горящего здания и тушу взрыватель. Это помогает на короткое время, но динамит взорвется в любом случае через некоторое время.
Конечно, могут быть ситуации, когда перехват общего исключения необходим, но только для целей отладки. Ошибки и ошибки должны быть исправлены, а не скрыты.