Мой конкретный случай здесь заключается в том, что пользователь может передать строку в приложение, приложение анализирует ее и назначает ее структурированным объектам. Иногда пользователь может ввести что-то недопустимое. Например, их вклад может описать человека, но они могут сказать, что их возраст - «яблоко». Правильное поведение в этом случае - откатить транзакцию и сообщить пользователю, что произошла ошибка, и ему придется повторить попытку. Может быть требование сообщать о каждой ошибке, которую мы можем найти во входных данных, а не только в первой.
В этом случае я утверждал, что мы должны выбросить исключение. Он не согласился, сказав: «Исключения должны быть исключительными: ожидается, что пользователь может ввести неверные данные, так что это не исключительный случай». Я действительно не знал, как аргументировать эту точку зрения, потому что по определению слова он похоже, прав.
Но я понимаю, что именно поэтому исключения были изобретены в первую очередь. Раньше вам приходилось проверять результат, чтобы увидеть, произошла ли ошибка. Если вы не смогли проверить, плохие вещи могут произойти без вашего ведома.
Без исключения каждый уровень стека должен проверять результат методов, которые они вызывают, и если программист забывает проверить один из этих уровней, код может случайно продолжить работу и сохранить недопустимые данные (например). Кажется, больше ошибок подвержены таким образом.
В любом случае, не стесняйтесь исправлять все, что я сказал здесь. Мой главный вопрос: если кто-то говорит, что исключения должны быть исключительными, как мне узнать, является ли мой случай исключительным?
источник
Ответы:
Исключения были придуманы, чтобы облегчить обработку ошибок с меньшим количеством беспорядка кода. Вы должны использовать их в тех случаях, когда они облегчают обработку ошибок с меньшим количеством помех в коде. Это «исключения только для исключительных обстоятельств» связано с тем, что обработка исключений считалась неприемлемым ударом по производительности. Это больше не относится к подавляющему большинству кода, но люди все еще распространяют правило, не помня причину его возникновения.
Особенно в Java, которая, возможно, является самым любящим исключение языком, когда-либо задуманным, вы не должны расстраиваться из-за использования исключений, когда это упрощает ваш код. Фактически, собственный
Integer
класс Java не имеет средств для проверки, является ли строка допустимым целым числом, без потенциального выбросаNumberFormatException
.Кроме того, хотя вы не можете полагаться только на валидацию пользовательского интерфейса, имейте в виду, если ваш пользовательский интерфейс спроектирован правильно, например, с использованием счетчика для ввода коротких числовых значений, тогда нечисловое значение, превращающее его в серверную часть, действительно будет исключительное состояние
источник
throw new ...
. Или создайте пользовательские исключения, где fillInStackTrace () перезаписывается. Тогда вы не должны замечать каких-либо ухудшений производительности, не говоря уже о хитах .if (foo() == ERROR) { return ERROR; } else { // continue }
на каждом уровне. Если вы выбрасываете непроверенное исключение, не будет шумного и избыточного «если ошибка вернет ошибку». Кроме того, если вы передаете функции в качестве аргументов, использование кода ошибки может изменить сигнатуру функции на несовместимый тип, даже если ошибка может не возникнуть.Когда должно быть выдано исключение? Когда дело доходит до кода, я думаю, что следующее объяснение очень полезно:
Исключением является случай, когда член не может выполнить задачу, которую он должен выполнить, как указано его именем . (Джеффри Рихтер, CLR через C #)
Почему это полезно? Это предполагает, что это зависит от контекста, когда что-то должно обрабатываться как исключение или нет. На уровне вызовов метода контекст задается (а) именем, (б) сигнатурой метода и (б) клиентским кодом, который использует или, как ожидается, будет использовать метод.
Чтобы ответить на ваш вопрос, вы должны взглянуть на код, где обрабатывается ввод пользователя. Это может выглядеть примерно так:
Предлагает ли название метода некоторую проверку? Нет. В этом случае недействительный PersonData должен выдать исключение.
Предположим, что у класса есть другой метод, который выглядит следующим образом:
Предлагает ли название метода некоторую проверку? Да. В этом случае недействительный PersonData не должен выдавать исключение.
Чтобы сложить все вместе, оба метода предполагают, что код клиента должен выглядеть следующим образом:
Когда неясно, должен ли метод генерировать исключение, возможно, это связано с неправильно выбранным именем или сигнатурой метода. Может быть, дизайн класса не ясно. Иногда вам нужно изменить дизайн кода, чтобы получить четкий ответ на вопрос, следует ли создавать исключение или нет.
источник
struct
называемый ValidationResult и структурировал свой код так, как вы описываете.Validate
(возвращает False, если недействительный) и один раз во времяSave
(выбрасывает конкретное, хорошо документированное исключение, если недействительный). Конечно, результат проверки может быть кэширован внутри объекта, но это добавит дополнительную сложность, так как результат проверки должен быть признан недействительным при изменениях.Validate()
вызываться внутриSave()
метода, и конкретные детали из негоValidationResult
могут использоваться для создания соответствующего сообщения для исключения.На этот аргумент:
Любое исключение, которое вы поймали, вы должны ожидать, потому что, ну, вы решили его поймать. И поэтому по этой логике вы никогда не должны выдавать какие-либо исключения, которые вы действительно планируете отлавливать.
Поэтому я думаю, что «исключения должны быть исключительными» - это ужасное правило.
Что вы должны сделать, зависит от языка. Разные языки имеют разные соглашения о том, когда следует создавать исключения. Например, Python генерирует исключения для всего, и когда я в Python, я следую его примеру. С ++, с другой стороны, выдает относительно немного исключений, и я следую их примеру. Вы можете относиться к C ++ или Java как к Python и генерировать исключения для всего, но ваша работа расходится с тем, как язык ожидает, что он будет использоваться.
Я предпочитаю подход Python, но считаю плохой идеей вводить в него другие языки.
источник
"exceptions should be exceptional" is a terrible rule of thumb.
Хорошо сказано! Это одна из тех вещей, которые люди просто повторяют, не думая о них.Когда я думаю об исключениях, я всегда думаю о таких вещах, как доступ к серверу базы данных или веб-API. Вы ожидаете, что сервер / веб-API будет работать, но в исключительном случае это может не произойти (сервер не работает). Обычно веб-запрос может быть быстрым, но в исключительных случаях (высокая нагрузка) он может истечь. Это что-то вне вашего контроля.
Входные данные ваших пользователей находятся под вашим контролем, так как вы можете проверить, что они отправляют, и делать с ними то, что вам нравится. В вашем случае я бы проверил введенные пользователем данные, даже не пытаясь их сохранить. И я склонен согласиться с тем, что следует ожидать появления пользователей, предоставляющих неверные данные, и ваше приложение должно учитывать их, проверяя входные данные и предоставляя удобное сообщение об ошибке.
Тем не менее, я использую исключения в большинстве моих установщиков моделей доменов, где не должно быть абсолютно никаких шансов на то, что неверные данные будут введены. Однако это последняя линия защиты, и я стараюсь создавать свои входные формы с богатыми правилами проверки. , так что практически нет шансов вызвать это исключение модели предметной области. Поэтому, когда сеттер ожидает одно, а получает другое, это исключительная ситуация, которой не должно было случиться в обычных обстоятельствах.
РЕДАКТИРОВАТЬ (что-то еще, чтобы рассмотреть):
Отправляя предоставленные пользователем данные в базу данных, вы заранее знаете, что следует и не следует вводить в свои таблицы. Это означает, что данные могут быть проверены в соответствии с некоторым ожидаемым форматом. Это то, что вы можете контролировать. То, что вы не можете контролировать, это сбой вашего сервера в середине вашего запроса. Таким образом, вы знаете, что запрос в порядке и данные фильтруются / проверяются, вы пытаетесь выполнить запрос, и он все равно не выполняется, это исключительная ситуация.
Аналогично веб-запросам, вы не можете знать, истечет ли время ожидания запроса или не удастся подключиться, прежде чем пытаться отправить его. Так что это также оправдывает подход try / catch, так как вы не можете спросить сервер, будет ли он работать через несколько миллисекунд после отправки запроса.
источник
FileNotFoundException
когда задан неправильный ввод (например, несуществующее имя файла). Это единственный допустимый способ угрозы этой ошибке, что еще вы можете сделать, не возвращая коды ошибок?Ссылка
От прагматичного программиста:
Они продолжают изучать пример открытия файла для чтения, а файл не существует - должно ли это вызвать исключение?
Позже они обсуждают, почему они выбрали этот подход:
Относительно вашей ситуации
Ваш вопрос сводится к "Должны ли ошибки валидации вызывать исключения?" Ответ заключается в том, что это зависит от того, где происходит проверка.
Если рассматриваемый метод находится в разделе кода, где предполагается, что входные данные уже проверены, недопустимые входные данные должны вызывать исключение; если код разработан так, что этот метод будет получать точные данные, введенные пользователем, следует ожидать неверные данные и исключение не должно возникать.
источник
Здесь много философских понтификаций, но, вообще говоря, исключительные условия - это просто те условия, которые вы не можете или не хотите обрабатывать (кроме очистки, сообщения об ошибках и т. П.) Без вмешательства пользователя. Другими словами, это безвозвратные условия.
Если вы передаете программе путь к файлу с намерением каким-то образом обработать этот файл, а файл, указанный по этому пути, не существует, это исключительное условие. Вы не можете ничего сделать с этим в своем коде, кроме как сообщить об этом пользователю и позволить ему указать другой путь к файлу.
источник
Есть две проблемы, которые вы должны рассмотреть:
вы обсуждаете одну проблему - давайте назовем ее,
Assigner
так как эта задача состоит в назначении входных данных для структурированных объектов - и вы выражаете ограничение, чтобы его входные данные были действительнымиУ хорошо реализованного пользовательского интерфейса есть дополнительная проблема: проверка пользовательского ввода и конструктивная обратная связь по ошибкам (давайте назовем эту часть
Validator
)С точки зрения
Assigner
компонента, исключение является вполне разумным, поскольку вы выразили ограничение, которое было нарушено.С точки зрения пользовательского опыта , пользователь не должен прямо говорить об этом
Assigner
. Они должны говорить с ним черезValidator
.Теперь, в случае
Validator
неправильного ввода пользователя, это не исключительный случай, это действительно тот случай, который вас больше интересует. Так что здесь исключение не будет уместным, и это также, где вы бы хотели определить все ошибки, а не выручать на первом.Вы заметите, я не упомянул, как эти проблемы реализованы. Кажется, вы говорите о,
Assigner
а ваш коллега говорит о комбинированномValidator+Assigner
. Как только вы поймете, что есть две отдельные (или отдельные) проблемы, по крайней мере, вы можете обсудить это разумно.Чтобы ответить на комментарий Ренана, я просто предполагаю, что после того, как вы определили свои две отдельные проблемы, становится очевидным, какие случаи следует рассматривать как исключительные в каждом контексте.
В самом деле, если это не очевидно , следует ли считать чем - то исключительным, я бы поспорил вы , вероятно , еще не закончили идентификацию независимых проблем в решении.
Я думаю, что это делает прямой ответ на
продолжайте упрощаться, пока это не станет очевидным . Когда у вас есть куча простых концепций, которые вы хорошо понимаете, вы можете ясно рассуждать о том, чтобы объединить их обратно в код, классы, библиотеки или что-то еще.
источник
Другие ответили хорошо, но все же вот мой короткий ответ. Исключением является ситуация, когда в среде что-то не так, что вы не можете контролировать, а ваш код вообще не может двигаться вперед. В этом случае вам также нужно будет сообщить пользователю, что пошло не так, почему вы не можете пойти дальше, и каково решение.
источник
Я никогда не был большим поклонником совета о том, что вы должны бросать исключения только в исключительных случаях, отчасти потому, что это ничего не говорит (это все равно, что говорить, что вы должны есть только пищу), но также потому, что это очень субъективно, и часто неясно, что представляет собой исключительный случай, а что нет.
Однако для этого совета есть веские причины: создание и отлов исключений происходит медленно, и если вы выполняете свой код в отладчике в Visual Studio с установленным для него уведомлением о каждом возникновении исключения, вы можете получить спам от десятков если не сотни сообщений задолго до того, как вы решите проблему.
Так что, как правило, если:
тогда ваш код никогда не должен выдавать исключение, даже то, которое перехватывается позже. Чтобы перехватить неверные данные, вы можете использовать валидаторы на уровне пользовательского интерфейса или код, например,
Int32.TryParse()
на уровне представления.Во всем остальном вы должны придерживаться принципа, что исключение означает, что ваш метод не может делать то, что его имя говорит, что он делает. В общем случае не рекомендуется использовать коды возврата для обозначения сбоя (если, например, имя вашего метода явно не указывает на это, например
TryParse()
) по двум причинам. Во-первых, ответ по умолчанию на код ошибки состоит в том, чтобы игнорировать условие ошибки и продолжать работу независимо; во-вторых, вы слишком легко можете получить некоторые методы, использующие коды возврата, и другие методы, использующие исключения, и забыв, что есть что. Я даже видел кодовые базы, где две разные взаимозаменяемые реализации одного и того же интерфейса используют разные подходы.источник
Исключения должны представлять условия, которые, вероятно, будет вызван непосредственным вызывающим кодом, даже если вызывающий метод может это сделать. Рассмотрим, например, код, который считывает некоторые данные из файла, может на законных основаниях предполагать, что любой допустимый файл заканчивается правильной записью, и не требуется извлекать какую-либо информацию из частичной записи.
Если подпрограмма чтения данных не использует исключения, а просто сообщает, успешно ли выполнено чтение, вызывающий код должен выглядеть следующим образом:
и т. д. тратить три строки кода на каждую полезную часть работы. Напротив, если
readInteger
will вызывает исключение при обнаружении конца файла, и если вызывающая сторона может просто передать исключение, то код становится:Гораздо проще и чище, с большим акцентом на случай, когда все работает нормально. Следует отметить , что в тех случаях , когда непосредственный вызывающий абонент будет ожидать , чтобы обработать условие, метод , который возвращает код ошибки часто будет более полезным , чем тот , который бросает исключение. Например, чтобы сложить все целые числа в файле:
против
Код, который запрашивает целые числа, ожидает, что один из этих вызовов потерпит неудачу. Использование кода, использующего бесконечный цикл, который будет выполняться до тех пор, пока это не произойдет, гораздо менее элегантно, чем использование метода, который указывает на сбои через возвращаемое значение.
Поскольку классы часто не будут знать, какие условия ожидают или не ожидают их клиенты, часто бывает полезно предложить две версии методов, которые могут дать сбой таким образом, чего ожидают некоторые вызывающие стороны, а другие - нет. Это позволит аккуратно использовать такие методы для обоих типов абонентов. Также обратите внимание, что даже методы "try" должны генерировать исключения, если возникают ситуации, которых вызывающий, вероятно, не ожидает. Например,
tryReadInteger
не следует выдавать исключение, если оно сталкивается с чистым условием конца файла (если вызывающий объект не ожидал этого, он бы использовалreadInteger
). С другой стороны, он, вероятно, должен выдать исключение, если данные не могут быть прочитаны, потому что, например, карта памяти, содержащая их, была отключена. Хотя такие события всегда следует признавать возможными, маловероятно, что код непосредственного вызова был бы готов что-либо сделать в ответ; это, конечно, не должно передаваться так же, как в конце файла.источник
Самое главное в написании программного обеспечения - сделать его читабельным. Все остальные соображения второстепенны, в том числе обеспечение эффективности и правильности. Если он читабелен, об остальном можно позаботиться при обслуживании, а если он не читается, то лучше просто выбросить его. Следовательно, вы должны генерировать исключения, когда это улучшает удобочитаемость.
Когда вы пишете какой-то алгоритм, просто подумайте о человеке в будущем, который будет его читать. Когда вы подходите к месту, где может возникнуть потенциальная проблема, спросите себя, хочет ли читатель посмотреть, как вы решаете эту проблему сейчас , или же читатель предпочел бы просто продолжить работу с алгоритмом?
Мне нравится думать о рецепте шоколадного торта. Когда он говорит вам добавить яйца, у него есть выбор: он может либо предположить, что у вас есть яйца, и продолжить рецепт, либо начать объяснение того, как вы можете получить яйца, если у вас нет яиц. Это может заполнить целую книгу техниками охоты на диких цыплят, которые помогут вам испечь пирог. Это хорошо, но большинство людей не хотят читать этот рецепт. Большинство людей предпочли бы просто предположить, что яйца доступны, и продолжить рецепт. Это суждение, которое авторы должны сделать, когда пишут рецепты.
Не может быть никаких гарантированных правил относительно того, что делает хорошее исключение и какие проблемы следует решать немедленно, потому что это требует от вас чтения мыслей вашего читателя. Лучшее, что вы когда-либо будете делать, это эмпирические правила, и «исключения - только для исключительных обстоятельств» - это довольно хорошее правило. Обычно, когда читатель читает ваш метод, он ищет, что метод будет делать в 99% случаев, и ему не хотелось бы, чтобы это было загромождено странными угловыми случаями, такими как обращение с пользователями, вводящими нелегальный ввод, и другие вещи, которые почти никогда не происходят. Они хотят видеть, как нормальный поток вашего программного обеспечения выкладывается напрямую, одна за другой, как будто проблем не бывает.
источник
Вот почему вы не можете выбросить здесь исключение. Исключение немедленно прерывает процесс проверки. Так что было бы много работы, чтобы сделать это.
Плохой пример:
Метод проверки для
Dog
класса с использованием исключений:Как это назвать:
Проблема здесь в том, что процесс проверки, чтобы получить все ошибки, потребует пропустить уже найденные исключения. Вышесказанное может работать, но это явное неправильное использование исключений . Тип проверки, о которой вас просили, должен быть проведен до того, как будет затронута база данных. Так что откатывать не нужно. И результаты валидации, скорее всего, будут ошибками валидации (но, надеюсь, нулевыми).
Лучший подход:
Вызов метода:
Метод проверки:
Почему? Есть множество причин, и большинство других было указано в других ответах. Проще говоря: гораздо проще читать и понимать других. Во-вторых, вы хотите показать следы стека пользователя, чтобы объяснить ему, что он настроил его
dog
неправильно?Если во время фиксации во втором примере все еще возникает ошибка , даже если ваш валидатор проверил
dog
с нулевыми ошибками , тогда выбрасывать исключение - это правильно . Как: Нет соединения с базой данных, запись в базе данных была изменена кем-то еще, или что-то подобное.источник