Стали бы вы когда-нибудь НЕ перехватывать исключение или выдавать исключение, которое не будет перехвачено?

StackOverflow https://stackoverflow.com/questions/666645

Вопрос

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

Или, по крайней мере, НЕ ловить исключение?

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

Это было полезно?

Решение

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

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

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

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

  • Что вы пытаетесь передать, выбрасывая исключение самостоятельно? Спросите себя, какова польза от создания нового исключения, и тщательно подумайте, нет ли лучшего способа сделать то, что вы хотите.

  • Если не перехватить исключение, ресурсы могут оказаться в плохом состоянии. Если вы не выйдете корректно, все, как правило, не будет очищено для вас.Убедитесь, что вы понимаете, что делаете, если вам нужно это сделать, а если вы не собираетесь этого делать, по крайней мере, рассмотрите возможность try-finally заблокируйте, чтобы вы могли немного навести порядок.

Другие советы

Есть очень хорошее правило, с которым я столкнулся некоторое время назад:

Вызовите исключение, когда метод не может сделать то, что говорит его имя.

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

Еще одно правило, которого следует придерживаться:

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

Очевидно, что вам следует включать код очистки в блок try...finally, но никогда не следует просто перехватывать исключение только ради его перехвата.И никогда не следует молча проглатывать исключения.Хотя бывают случаи, когда вам может потребоваться перехватить все исключения (например,выполнив catch (Exception ex) в C#), они встречаются довольно редко и обычно имеют очень конкретную техническую причину.Например, если вы используете потоки в .NET 2.0 или более поздней версии, то исключение из вашего потока приведет к выгрузке всего домена приложения.Однако в этих случаях вам следует как минимум зарегистрировать сведения об исключении как ошибку и предоставить объяснение в комментариях.

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

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

Поэтому я бы посоветовал вам вместо этого спросить: «Когда мне следует перехватить исключение?»

Конечно.Например, если вы пытаетесь загрузить несколько байтов в строку в Java:

try {
  String myString = new String(byteArray, "UTF-8");
} catch (UnsupportedEncodingException e) {
  // Platform doesn't support UTF-8?  What is this, 1991?
  throw new RuntimeExceptione(e);
}

В этом случае постепенной деградации не происходит, платформа просто не может поддерживать желаемую операцию.Вы можете сколько угодно проверять это условие при инициализации, но конструктор String все равно выдает это исключение, и вам придется с ним иметь дело.Либо так, либо используйте Charset.forName() :)

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

А если по какой-то причине он не может завершить свою работу, то ему необходимо сказать вызывающему абоненту: «Извините, я не смог этого сделать, и вот почему».Исключения — отличный механизм, позволяющий сообщить об этом вызывающей стороне (не единственный механизм, но в большинстве случаев лучший механизм, который я когда-либо видел).

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

Перехват исключения — это работа «контекста».Например, предположим, что вы пишете библиотеку с общедоступными методами, которые могут генерировать исключения.Затем предположим, что вы используете эту библиотеку из приложения Windows Forms.Приложение Windows Forms может перехватывать исключения и показывать пользователю окно сообщения.

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

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

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

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

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

Вы бы когда-нибудь НЕ поймали исключение?

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

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

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

например

A вызывает B вызывает C (выдает исключение)

Б ловит/перебрасывает

А ловит.

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

В общем, вы почти НИКОГДА не захотите, чтобы исключение уничтожило вашу программу.Лучшая практика — перехватить исключение и корректно выйти.Это позволяет вам сохранять любую открытую в данный момент информацию и освобождать используемые ресурсы, чтобы они не были повреждены.Если вы собираетесь выйти, вы можете создать свой собственный информационный отчет «дамп ядра», включающий все, что вы делали, когда обнаружили фатальное исключение.

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

Итак, я бы рекомендовал ВСЕГДА перехватывать исключения и никогда добровольно не позволять им выходить из-под контроля в вашей программе.

РЕДАКТИРОВАТЬ

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

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

Иногда исключение может сказать вам, что ваша программа находится в неизвестном состоянии.Существует ряд исключений, в которых это в значительной степени верно, учитывая тип исключения.А NullReferenceException по сути говорит вам: «Есть ошибка».И, перехватив такое исключение, вы можете скрыть ошибку, что в краткосрочной перспективе звучит хорошо, но в долгосрочной перспективе вы будете счастливы ее исправить.Возможно, продукт не выйдет из строя, но он определенно не будет вести себя ожидаемым образом.

Но это также верно и для типов исключений, которые мы изобретаем для себя.Иногда тот факт, что было выброшено исключение A, должен быть «невозможным» - и все же это произошло, значит, есть ошибка.

Кроме того, когда вы ловите исключение, происходит нечто очень важное:тот finally блоки для всего стека вызовов внутри блока try (и всего, что он вызывает) будут выполнены.Что делают эти блокиfinally?Ну, что угодно.И если программа находится в неизвестном состоянии, я действительно имею в виду что-либо.Они могли стереть с диска ценные данные клиентов.Они могли бы генерировать больше исключений.Они могут повредить данные в памяти, что сделает невозможным диагностику ошибки.

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

Во-вторых, следует ли создавать исключение, зная, что оно не будет перехвачено?Я думаю, что этот вопрос неправильно понимает природу многоразовых методов.Вся идея метода заключается в том, что у него есть «контракт», которому он следует:он принимает определенные параметры и возвращает определенное значение, а также выдает определенные исключения при определенных условиях.Таков контракт: что с ним делать, зависит от вызывающего абонента.Для некоторых вызывающих абонентов исключение A может указывать на исправимое состояние.Для других вызывающих абонентов это может указывать на ошибку.И из того, что я сказал выше, должно быть ясно, что если исключение указывает на ошибку, его нельзя поймать.

И если вам интересно, что это значит для Блок обработки исключений корпоративной библиотеки Microsoft:да, он довольно сломан.Они говорят вам catch (Exception x) а затем решите, следует ли выполнять повторную отправку, в соответствии с вашей политикой;слишком поздно - finally к этому моменту блоки уже выполнились.Не делай этого.

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

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

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

Это зависит от того, что вы подразумеваете под «пойманным».Что-то где-то в конечном итоге ловит исключение, будь то базовая ОС или что-то еще.

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

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

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

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

Если вы пишете код API или код Framework, который не будете использовать сами, вы понятия не имеете, уловит ли кто-нибудь ваши исключения.

Да, это моя ЕДИНСТВЕННАЯ возможность дать пощечину разработчику, использующему сервис/объект, и сказать им: «Эй, делай это НЕПРАВИЛЬНО!!!!».

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

Если мне нужна система среднего размера, которая каким-то образом обрабатывает данные, как я считаю, согласованно.

И

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

И

Система (пока) не знает, как исправить несогласованность и корректно восстановиться.

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top