Использование try-finally (без перехвата) против проверки состояния перечисления

9

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

Моя дилемма в отношении лучшей практики заключается в том, следует ли использовать try / catch / finally для возврата перечисления (или типа int, представляющего значение, 0 для ошибки, 1 для подтверждения, 2 для предупреждения и т. Д., В зависимости от случая), чтобы ответ всегда в порядке, или нужно пропустить исключение, чтобы вызывающая часть справилась с ним?

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

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

Хотя можно попробовать перехватить исключение 404 внутри вспомогательной функции, которая получает / публикует данные, не так ли? Только я использую smallint для обозначения состояний в программе (и, конечно, для их надлежащего документирования), а затем использую эту информацию для целей проверки работоспособности (все в порядке / обработка ошибок) снаружи?

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

Опять же, с примером http get / post, вопрос в том, должны ли вы предоставить новый объект, который описывает, что случилось с исходным абонентом? Если бы этот помощник находился в используемой вами библиотеке, вы ожидаете, что он предоставит вам код состояния для операции, или вы включите его в блок try-catch? Если вы разрабатываете его, вы бы предоставили код состояния или сгенерировали исключение и позволили верхнему уровню преобразовать его в код состояния / сообщение вместо этого?

Сводка: Как вы выбрали, если часть кода вместо создания исключения возвращает код состояния вместе с любыми результатами, которые он может дать?

Михалис Багос
источник
1
Это не касается ошибки, которая меняет используемую форму обработки ошибок. В случае 404 вы позволите ему пройти, потому что вы не можете справиться с этим.
Stonemetal
«int, представляющее значение, 0 для ошибки, 1 для ОК, 2 для предупреждения и т. д.» Скажите, пожалуйста, что это был случайный пример! Использование 0 для обозначения ОК - это определенно стандарт ...
anaximander

Ответы:

15

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

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

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

Роберт Харви
источник
Спасибо за ответ, он самый информативный, но я сосредоточен на обработке исключений, а не на их исключении. Должны ли вы поймать исключение 404, как только вы его получите, или вы должны позволить ему подняться выше по стеку?
Михалис Багос
Я вижу ваши изменения, но это не меняет моего ответа. Преобразуйте исключение в код ошибки, если это имеет значение для вызывающей стороны. Если это не так, обработайте исключение; пусть он поднимется в стек; или поймать его, сделать что-то с ним (например, записать его в журнал или что-то еще), и сбросить.
Роберт Харви
1
+1: для разумного и сбалансированного объяснения. Исключение следует использовать для обработки исключительных случаев. В противном случае функция или метод должны возвращать значимое значение (перечисление или тип параметра), и вызывающая сторона должна обрабатывать его правильно. Возможно, можно упомянуть третью альтернативу, популярную в функциональном программировании, то есть продолжения.
Джорджио
4

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

Например, System.IO.File.OpenRead () вызовет исключение FileNotFoundException, если предоставленный файл не существует, однако он также предоставляет метод .Exists (), который возвращает логическое значение, указывающее, присутствует ли файл, который следует вызвать перед вызов OpenRead (), чтобы избежать любых неожиданных исключений.

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

Тревор Пилли
источник
Многие люди, особенно программисты на python , предпочитают EAFP, т. Е. « Прощения проще попросить, чем получить разрешение»
Марк Бут,
1
+1 за комментарий об избежании исключений как .Exists ().
codingoutloud
+1 Это еще хороший совет. В коде, который я пишу / управляю, Исключение является «Исключительным», 9/10 раз Исключение предназначено для того, чтобы разработчик мог увидеть, говорит он: «Вы должны быть защитниками программирования! В другой раз, это то, с чем мы не можем иметь дело, и мы регистрируем это и выходим как можно лучше. Согласованность важна, например, условно, что у нас обычно был бы истинный ложный ответ и внутренние сообщения для стандартного тарифа / обработки. API сторонних разработчиков, которые, кажется, генерируют исключения для всего, могут быть обработаны при вызове и возвращены с использованием стандартного согласованного процесса.
Гэвин Хоуден
3

используйте try / catch / finally, чтобы вернуть enum (или int, представляющий значение, 0 для ошибки, 1 для ok, 2 для предупреждения и т. д., в зависимости от случая), чтобы ответ всегда был в порядке,

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

или нужно пропустить исключение, чтобы вызывающая часть справилась с ним?

Вот для чего и исключения.

иметь дело как можно ближе к тому месту, где он поднят

Это не универсальная правда вообще. Иногда это хорошая идея. В других случаях это не так полезно.

С. Лотт
источник
Это не ужасный дизайн. Microsoft реализует его во многих местах, а именно у поставщика членства asp.NET по умолчанию. Кроме этого я не вижу, как этот ответ способствует разговору
Михалис Багос
@MihalisBagos: Все, что я могу сделать, - это предположить, что подход Microsoft не поддерживается всеми языками программирования. В языках без исключений возврат значения является обязательным. C является наиболее ярким примером. В языках с исключениями возврат «значений кода» для обозначения ошибок - ужасный дизайн. Это приводит к (иногда) громоздким ifоператорам вместо (иногда) более простой обработки исключений. Ответ («Как выбрать ...») прост. Не . Я думаю, сказав это добавляет к разговору. Вы можете игнорировать этот ответ, однако.
S.Lott
Я не говорю, что ваше мнение не считается, но я говорю, что ваше мнение не развито. С этим комментарием, я так понимаю, причина в том, что когда мы можем использовать исключения, мы должны просто потому, что можем? Я не вижу код состояния как маскирующий, а не классификацию / организацию случаев потока кода
Михалис Багос
1
Исключение уже является категоризацией / организацией. Я не вижу значения в замене однозначного исключения возвращаемым значением, которое можно легко спутать с «нормальными» или «неисключительными» возвращаемыми значениями. Возвращаемые значения всегда должны быть неисключительными. Мое мнение не очень развито: все очень просто. Не преобразовывайте исключение в числовой код. Это был уже совершенно хороший объект данных, который обслуживает все прекрасно используемые сценарии использования, которые мы можем себе представить.
S.Lott
3

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

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

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

DwayneAllen
источник
1

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

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

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

Итак, мой вопрос к ОП заключается в том, почему вы НЕ хотите использовать исключения при возврате кодов ошибок?

S.Robins
источник
0

Все хорошие ответы. Я также хотел бы добавить, что возврат кода ошибки вместо выдачи исключения может усложнить код вызывающей стороны. Скажем, метод A вызывает метод B, вызывает метод C, и C сталкивается с ошибкой. Если C возвращает код ошибки, теперь B должен иметь логику, чтобы определить, может ли он обработать этот код ошибки. Если это невозможно, то нужно вернуть его A. Если A не может обработать ошибку, что вы делаете? Бросить исключение? В этом примере код становится намного чище, если C просто выдает исключение, B не перехватывает исключение, поэтому он автоматически прерывается без какого-либо дополнительного кода, необходимого для этого, и A может перехватывать определенные типы исключений, позволяя другим продолжать вызов стек.

Это напоминает хорошее правило:

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

Рэймонд Салтрелли
источник
0

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

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

Например

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

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

if not Validate() then
  DoNotContinue();

но он может забыть и только позвонить Validate()(я знаю, что он не должен, но, возможно, он мог).

Итак, в приведенном выше коде я получил два преимущества:

  1. Только одно исключение в функции проверки.
  2. Исключение, даже необработанное, остановит выполнение и появится во время теста
Bahaa
источник
0

Здесь нет одного ответа - вроде как нет одного вида HttpException.

Имеет большой смысл, что базовые HTTP-библиотеки выдают исключение, когда получают ответ 4xx или 5xx; В прошлый раз, когда я смотрел на спецификации HTTP, это были ошибки.

Что касается того, чтобы выбросить это исключение - или обернуть его и перебросить - я думаю, что это действительно вопрос использования. Например, если вы пишете оболочку для сбора некоторых данных из API и предоставления их приложениям, вы можете решить, что семантически запрос на несуществующий ресурс, который возвращает HTTP 404, имеет больше смысла, чтобы перехватить это и вернуть null. С другой стороны, ошибка 406 (недопустимая) может стоить выдать ошибку, поскольку это означает, что что-то изменилось, и приложение должно аварийно завершить работу, гореть и кричать о помощи.

Уайетт Барнетт
источник