Мне сказали, что исключения должны использоваться только в исключительных случаях. Как я узнаю, является ли мой случай исключительным?

99

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

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

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

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

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

Даниэль Каплан
источник
3
возможный дубликат? Когда бросить исключение . Хотя он был закрыт там, но я думаю, что он подходит здесь. Это все еще немного философии, некоторые люди и сообщества склонны рассматривать исключения как своего рода управление потоком.
Торстен Мюллер
8
Когда пользователи тупые, они вводят неверные данные. Когда пользователи умны, они играют, предоставляя неверный ввод. Поэтому неверный ввод пользователя не является исключением.
Mouviciel
7
Кроме того, не путайте исключение , которое является очень специфическим механизмом в Java и .NET, с ошибкой, которая является гораздо более общим термином. Там больше обработки ошибок, чем бросать исключения. Это обсуждение затрагивает нюансы между исключениями и ошибками .
Эрик Кинг,
4
"Исключительно"! = "Редко случается"
ConditionRacer
3
Я нахожу исключение Эрика Липперта Vexing достойным советом.
Брайан

Ответы:

87

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

Особенно в Java, которая, возможно, является самым любящим исключение языком, когда-либо задуманным, вы не должны расстраиваться из-за использования исключений, когда это упрощает ваш код. Фактически, собственный Integerкласс Java не имеет средств для проверки, является ли строка допустимым целым числом, без потенциального выброса NumberFormatException.

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

Карл Билефельдт
источник
10
Хороший удар, там. На самом деле, в реальном приложении, которое я разработал, снижение производительности действительно имело значение, и мне пришлось изменить его, чтобы не генерировать исключения для определенных операций синтаксического анализа.
Роберт Харви
17
Я не говорю, что до сих пор нет случаев, когда снижение производительности является уважительной причиной, но эти случаи являются скорее исключением (каламбуром), чем правилом.
Карл Билефельдт
11
@RobertHarvey Хитрость в Java состоит в том, чтобы бросать заранее изготовленные объекты исключений, а не throw new .... Или создайте пользовательские исключения, где fillInStackTrace () перезаписывается. Тогда вы не должны замечать каких-либо ухудшений производительности, не говоря уже о хитах .
Инго
3
+1: именно то, что я собирался ответить. Используйте его, когда это упрощает код. Исключения могут дать гораздо более понятный код, когда вам не нужно проверять возвращаемые значения на каждом уровне в стеке вызовов. (Как и все остальное, хотя, если использовать его неправильно, это может сделать ваш код ужасным беспорядком.)
Лев
4
@Brendan Предположим, что произошло какое-то исключение, а код обработки ошибок на 4 уровня ниже в стеке вызовов. Если вы используете код ошибки, все 4 функции над обработчиком должны иметь тип кода ошибки в качестве возвращаемого значения, и вы должны выполнить цепочку if (foo() == ERROR) { return ERROR; } else { // continue }на каждом уровне. Если вы выбрасываете непроверенное исключение, не будет шумного и избыточного «если ошибка вернет ошибку». Кроме того, если вы передаете функции в качестве аргументов, использование кода ошибки может изменить сигнатуру функции на несовместимый тип, даже если ошибка может не возникнуть.
Довал
72

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

Исключением является случай, когда член не может выполнить задачу, которую он должен выполнить, как указано его именем . (Джеффри Рихтер, CLR через C #)

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

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

public void Save(PersonData personData) {  }

Предлагает ли название метода некоторую проверку? Нет. В этом случае недействительный PersonData должен выдать исключение.

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

public ValidationResult Validate(PersonData personData) {  }

Предлагает ли название метода некоторую проверку? Да. В этом случае недействительный PersonData не должен выдавать исключение.

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

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

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

Тео Ленндорф
источник
Буквально вчера я сделал так structназываемый ValidationResult и структурировал свой код так, как вы описываете.
Павел
4
Это не поможет ответить на ваш вопрос, но я просто хотел бы отметить, что вы неявно или намеренно следовали принципу разделения команд и запросов ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Тео Ленндорф,
Хорошая идея! Один недостаток: в вашем примере проверка фактически выполняется дважды: один раз во время Validate(возвращает False, если недействительный) и один раз во время Save(выбрасывает конкретное, хорошо документированное исключение, если недействительный). Конечно, результат проверки может быть кэширован внутри объекта, но это добавит дополнительную сложность, так как результат проверки должен быть признан недействительным при изменениях.
Хайнци
@ Heinzi Я согласен. Он может быть реорганизован так, чтобы Validate()вызываться внутри Save()метода, и конкретные детали из него ValidationResultмогут использоваться для создания соответствующего сообщения для исключения.
Фил
3
Это лучше, чем принятый ответ, я думаю. Брось, когда звонок не может сделать то, что должен был сделать.
Энди
31

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

На этот аргумент:

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

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

Поэтому я думаю, что «исключения должны быть исключительными» - это ужасное правило.

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

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

Уинстон Эверт
источник
1
@gnat, я знаю. Я хотел сказать, что вы должны следовать правилам языка (в данном случае Java), даже если они не ваши любимые.
Уинстон Эверт
6
+1 "exceptions should be exceptional" is a terrible rule of thumb.Хорошо сказано! Это одна из тех вещей, которые люди просто повторяют, не думая о них.
Андрес Ф.
2
«Ожидаемый» определяется не субъективным аргументом или соглашением, а контрактом API / функции (это может быть явным, но часто подразумеваемым). Разные функции / API / подсистемы могут иметь разные ожидания, например, для некоторой функциональности более высокого уровня ожидаемый случай для него - несуществующий файл (он может сообщить об этом пользователю через GUI), для других функций более низкого уровня - Вероятно, нет (и, следовательно, следует выбросить исключение). Этот ответ, кажется, упускает этот важный момент ....
Микера
1
@mikera, да, функция должна (только) генерировать исключения, определенные в ее контракте. Но это не вопрос. Вопрос в том, как решить, каким должен быть этот контракт. Я утверждаю, что эмпирическое правило «исключения должны быть исключительными» не помогает при принятии этого решения.
Уинстон Эверт
1
@supercat, я не думаю, что это действительно имеет значение, что в конечном итоге встречается чаще. Я думаю, что критическим вопросом является наличие нормального дефолта. Если я явно не обработаю условие ошибки, мой код делает вид, что ничего не произошло, или я получаю полезное сообщение об ошибке?
Уинстон Эверт
30

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

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

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

РЕДАКТИРОВАТЬ (что-то еще, чтобы рассмотреть):

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

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

Иван Пинтар
источник
8
Но почему? Почему исключения менее полезны при обработке проблем, которые более ожидаемы?
Уинстон Эверт
6
@Pinetree, проверка существования файла перед открытием файла - плохая идея. Файл может перестать существовать между проверкой и открытием, файл не может иметь разрешения, позволяющего открыть его, а проверка на наличие и последующее открытие файла потребует двух дорогостоящих системных вызовов. Вам лучше попытаться открыть файл, а затем иметь дело с невозможностью сделать это.
Уинстон Эверт
10
Насколько я вижу, почти все возможные сбои лучше обрабатывать как восстановление после сбоя, а не пытаться проверить его на предмет успеха. Независимо от того, используете ли вы исключения или что-то еще, указывает на ошибку, это отдельная проблема. Я предпочитаю исключения, потому что я не могу случайно их игнорировать.
Уинстон Эверт
11
Я не согласен с вашей предпосылкой, что поскольку ожидаются неверные пользовательские данные, их нельзя считать исключительными. Если я пишу парсер, и кто-то передает ему непарсируемые данные, это исключение. Я не могу продолжить разбор. Как обрабатывается исключение - это совсем другой вопрос.
ConditionRacer
4
File.ReadAllBytes будет выдавать, FileNotFoundExceptionкогда задан неправильный ввод (например, несуществующее имя файла). Это единственный допустимый способ угрозы этой ошибке, что еще вы можете сделать, не возвращая коды ошибок?
oɔɯǝɹ
16

Ссылка

От прагматичного программиста:

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

Они продолжают изучать пример открытия файла для чтения, а файл не существует - должно ли это вызвать исключение?

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

Позже они обсуждают, почему они выбрали этот подход:

Исключение [A] n представляет собой немедленную нелокальную передачу управления - это своего рода каскад goto. Программы, которые используют исключения как часть своей обычной обработки, страдают от всех проблем с удобочитаемостью и удобством сопровождения классического кода спагетти. Эти программы нарушают инкапсуляцию: подпрограммы и их вызывающие стороны более тесно связаны посредством обработки исключений.

Относительно вашей ситуации

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

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

Майк Партридж
источник
11

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

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

Роберт Харви
источник
1
+1, очень близко к тому, что я собирался сказать. Я бы сказал, что это больше о сфере действия, и на самом деле не имеет ничего общего с пользователем. Хорошим примером этого является различие между двумя функциями .Net int.Parse и int.TryParse, у первого нет другого выбора, кроме как генерировать исключение при неправильном вводе, последующему никогда не следует генерировать исключение
jmoreno
1
@jmoreno: Ergo, вы бы использовали TryParse, когда код мог бы что-то сделать с непарсируемым условием, и Parse, когда это не могло.
Роберт Харви
7

Есть две проблемы, которые вы должны рассмотреть:

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

  2. У хорошо реализованного пользовательского интерфейса есть дополнительная проблема: проверка пользовательского ввода и конструктивная обратная связь по ошибкам (давайте назовем эту часть Validator)

С точки зрения Assignerкомпонента, исключение является вполне разумным, поскольку вы выразили ограничение, которое было нарушено.

С точки зрения пользовательского опыта , пользователь не должен прямо говорить об этом Assigner. Они должны говорить с ним черезValidator .

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

Вы заметите, я не упомянул, как эти проблемы реализованы. Кажется, вы говорите о, Assignerа ваш коллега говорит о комбинированном Validator+Assigner. Как только вы поймете, что есть две отдельные (или отдельные) проблемы, по крайней мере, вы можете обсудить это разумно.


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

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

Я думаю, что это делает прямой ответ на

... как мне узнать, является ли мой случай исключительным?

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

Бесполезный
источник
-1 Да, есть две проблемы, но это не отвечает на вопрос «Как узнать, является ли мой случай исключительным?»
RMalke
Дело в том, что один и тот же случай может быть исключительным в одном контексте, а не в другом. Определение контекста, о котором вы на самом деле говорите (а не их обоих), отвечает на вопрос здесь.
бесполезно
... на самом деле, возможно, это не так - вместо этого я изложил вашу точку зрения в своем ответе.
бесполезно
4

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

Маной Р
источник
3

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

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

Так что, как правило, если:

  • Ваш код не содержит ошибок, и
  • все услуги, от которых это зависит, и
  • ваш пользователь использует вашу программу так, как она была задумана (даже если некоторые из предоставленных им данных недействительны)

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

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

jammycakes
источник
2

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

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

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

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

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

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

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

против

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

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

Поскольку классы часто не будут знать, какие условия ожидают или не ожидают их клиенты, часто бывает полезно предложить две версии методов, которые могут дать сбой таким образом, чего ожидают некоторые вызывающие стороны, а другие - нет. Это позволит аккуратно использовать такие методы для обоих типов абонентов. Также обратите внимание, что даже методы "try" должны генерировать исключения, если возникают ситуации, которых вызывающий, вероятно, не ожидает. Например, tryReadIntegerне следует выдавать исключение, если оно сталкивается с чистым условием конца файла (если вызывающий объект не ожидал этого, он бы использовалreadInteger). С другой стороны, он, вероятно, должен выдать исключение, если данные не могут быть прочитаны, потому что, например, карта памяти, содержащая их, была отключена. Хотя такие события всегда следует признавать возможными, маловероятно, что код непосредственного вызова был бы готов что-либо сделать в ответ; это, конечно, не должно передаваться так же, как в конце файла.

Supercat
источник
2

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

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

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

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

Geo
источник
2

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

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

Плохой пример:

Метод проверки для Dogкласса с использованием исключений:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Как это назвать:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

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

Лучший подход:

Вызов метода:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Метод проверки:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

Почему? Есть множество причин, и большинство других было указано в других ответах. Проще говоря: гораздо проще читать и понимать других. Во-вторых, вы хотите показать следы стека пользователя, чтобы объяснить ему, что он настроил его dogнеправильно?

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

Матиас Ронге
источник