Еще в конце 90-х я довольно много работал с базой кода, которая использовала исключения в качестве управления потоком. В нем реализован конечный автомат для управления приложениями телефонии. В последнее время мне вспоминаются те дни, потому что я занимался веб-приложениями MVC.
У них обоих есть Controller
s, которые решают, куда идти дальше, и предоставляют данные логике назначения. Действия пользователя из домена телефона старой школы, такие как тоны DTMF, стали параметрами для методов действия, но вместо того, чтобы возвращать что-то вроде a ViewResult
, они бросали a StateTransitionException
.
Я думаю, что основным отличием было то, что методы действия были void
функциями. Я не помню всех вещей, которые я сделал с этим фактом, но я не решался даже пойти по дороге, вспоминая многое, потому что с той работы, как 15 лет назад, я никогда не видел этого в рабочем коде на любой другой работе . Я предположил, что это был знак того, что это был так называемый анти-паттерн.
Так ли это, и если да, то почему?
Обновление: когда я задавал вопрос, я уже имел в виду ответ @ MasonWheeler, поэтому я выбрал ответ, который больше всего увеличил мои знания. Я думаю, что это хороший ответ.
источник
Ответы:
Там есть подробное обсуждение этого на Wiki Уорда . Как правило, использование исключений для управления потоком является анти-моделью, со многими заметными ситуациями - и для конкретного языка (см , например , Python ) кашель исключение кашля .
В качестве краткого изложения, почему, как правило, это анти-шаблон:
Прочитайте обсуждение в вики Ward для получения более подробной информации.
Смотрите также дубликат этого вопроса, здесь
источник
if
аforeach
также сложныеGOTO
s. Честно говоря, я думаю, что сравнение с goto не полезно; это почти как уничижительный термин. Использование GOTO не является по сути злым - оно имеет реальные проблемы, и исключения могут разделять их, но они не могут. Было бы более полезно услышать эти проблемы.Вариант использования, для которого были разработаны исключения: «Я только что столкнулся с ситуацией, с которой я не могу должным образом разобраться на данном этапе, потому что у меня нет достаточного контекста для его обработки, но есть подпрограмма, которая вызвала меня (или что-то еще дальше по вызову»). стек) должен знать, как справиться с этим ".
Дополнительный вариант использования: «Я только что столкнулся с серьезной ошибкой, и сейчас выход из этого потока управления для предотвращения повреждения данных или другого повреждения важнее, чем попытка продолжить».
Если вы не используете исключения по одной из этих двух причин, возможно, есть лучший способ сделать это.
источник
const myvar = theFunction
потому что myvar должен быть создан из try-catch, таким образом, оно больше не является постоянным. Это не значит, что я не использую его в C #, так как он по какой-то причине является мейнстримом, но я все равно пытаюсь уменьшить их.Исключения так же сильны, как и продолжения
GOTO
. Они являются универсальной конструкцией потока управления.В некоторых языках они являются единственной универсальной конструкцией потока управления. В JavaScript, например, нет ни продолжения, ни
GOTO
даже правильных вызовов. Итак, если вы хотите реализовать сложный поток управления в JavaScript, вы должны использовать исключения.Проект Microsoft Volta был (в настоящее время прекращен) исследовательским проектом по компиляции произвольного кода .NET в JavaScript. В .NET есть исключения, чья семантика точно не соответствует JavaScript, но, что более важно, в ней есть потоки , и вы должны каким-то образом сопоставить их с JavaScript. Вольта сделал это, внедрив Volta Continuations с использованием исключений JavaScript, а затем реализовав все конструкции потока управления .NET в терминах Volta Continuations. Они должны были использовать исключения в качестве потока управления, потому что нет другой конструкции потока управления, достаточно мощной.
Вы упомянули государственные машины. SM являются тривиальными для реализации с надлежащим вызовом хвоста: каждое состояние - это подпрограмма, каждый переход состояния - это вызов подпрограммы. SM также могут быть легко реализованы с помощью
GOTO
или сопрограмм или продолжений. Однако, Java не имеет какого - либо из этих четырех, но это действительно есть исключения. Таким образом, вполне приемлемо использовать их в качестве потока управления. (Ну, на самом деле, правильный выбор, вероятно, заключался бы в использовании языка с правильной конструкцией потока управления, но иногда вы можете застрять в Java.)источник
Как уже неоднократно упоминали другие ( например, в этом вопросе о переполнении стека ), принцип наименьшего удивления запрещает чрезмерное использование исключений только для целей потока управления. С другой стороны, ни одно правило не является на 100% правильным, и всегда есть случаи, когда исключением является «просто правильный инструмент» -
goto
кстати, очень похожий на самого себя, который поставляется в формеbreak
иcontinue
на языках, таких как Java, что часто являются идеальным способом выпрыгнуть из сильно вложенных циклов, которых не всегда можно избежать.Следующий пост в блоге объясняет довольно сложный, но также довольно интересный вариант использования для нелокального
ControlFlowException
:Он объясняет, как внутри jOOQ (библиотеки абстракций SQL для Java) (отказ от ответственности: я работаю для поставщика) такие исключения иногда используются для прерывания процесса рендеринга SQL на ранних этапах, когда выполняется какое-то «редкое» условие.
Примеры таких условий:
Слишком много значений связывания. Некоторые базы данных не поддерживают произвольное количество значений связывания в своих операторах SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). В этих случаях jOOQ прерывает фазу рендеринга SQL и повторно отображает SQL-оператор со встроенными значениями связывания. Пример:
Если бы мы явно извлекали значения связывания из запроса AST, чтобы подсчитывать их каждый раз, мы бы тратили ценные циклы ЦП на те 99,9% запросов, которые не страдают от этой проблемы.
Некоторая логика доступна только косвенно через API, который мы хотим выполнить только «частично».
UpdatableRecord.store()
Метод генерируетINSERT
илиUPDATE
заявление, в зависимости отRecord
«с внутренними флагами. Со стороны мы не знаем, в какой логике содержитсяstore()
(например, оптимистическая блокировка, обработка прослушивателя событий и т. Д.), Поэтому мы не хотим повторять эту логику, когда мы храним несколько записей в пакетном операторе, где мы хотели быstore()
только генерировать оператор SQL, а не выполнять его на самом деле. Пример:Если бы мы вывели
store()
логику в «повторно используемый» API, который можно настроить так, чтобы он не выполнял SQL, мы бы искали создание довольно сложного в обслуживании, едва ли повторно используемого API.Заключение
По сути, мы используем эти нелокальные
goto
s только в соответствии с тем, что Мейсон Уилер сказал в своем ответе:Оба использования
ControlFlowExceptions
были довольно просты в реализации по сравнению с их альтернативами, что позволило нам повторно использовать широкий спектр логики без рефакторинга ее из соответствующих внутренних компонентов.Но ощущение того, что это будет сюрпризом для будущих сопровождающих, остается. Код кажется довольно деликатным, и, хотя в данном случае это был правильный выбор, мы всегда предпочли бы не использовать исключения для локального потока управления, где легко избежать использования обычного ветвления
if - else
.источник
Использование исключений для потока управления обычно считается антишаблоном, но есть исключения (не каламбур).
Тысячу раз говорили, что исключения предназначены для исключительных условий. Разрыв связи с базой данных является исключительным условием. Пользователь, вводящий буквы в поле ввода, которое должно разрешать только цифры , не является .
Ошибка в вашем программном обеспечении, приводящая к тому, что функция вызывается с недопустимыми аргументами, например,
null
где это не разрешено, является исключительным условием.Используя исключения для чего-то, что не является исключительным, вы используете неподходящие абстракции для проблемы, которую вы пытаетесь решить.
Но может быть и снижение производительности. В некоторых языках реализована более или менее эффективная реализация обработки исключений, поэтому, если выбранный вами язык не имеет эффективной обработки исключений, он может быть очень дорогостоящим, с точки зрения производительности *.
Но другие языки, например Ruby, имеют синтаксис, напоминающий исключения, для потока управления. Исключительные ситуации обрабатываются операторами
raise
/rescue
. Но вы можете использоватьthrow
/catch
для исключительных конструкций управления потоком **.Таким образом, хотя исключения обычно не используются для потока управления, у вашего языка выбора могут быть другие идиомы.
* Пример высокопроизводительного использования исключений: однажды я был настроен на оптимизацию плохо работающего приложения веб-формы ASP.NET. Оказалось, что рендеринг большой таблицы вызывал
int.Parse()
ок. тысяча пустых строк на средней странице, в результате ок. обрабатывается тысяча исключений. Заменив код наint.TryParse()
Я сбрил одну секунду! За каждый запрос страницы!** Это может быть очень запутанным для программиста подходит к Руби из других языков, как
throw
иcatch
ключевые слова , связанные с исключениями во многих других языках.источник
Вполне возможно обработать условия ошибки без использования исключений. Некоторые языки, особенно C, даже не имеют исключений, и людям все еще удается создавать довольно сложные приложения с его помощью. Причина, по которой исключения полезны, заключается в том, что они позволяют вам кратко указать два по существу независимых потока управления в одном и том же коде: один, если возникает ошибка, и один, если нет. Без них вы получите код повсюду, который выглядит следующим образом:
Или эквивалентный на вашем языке, например, возвращение кортежа с одним значением в качестве состояния ошибки и т. Д. Часто люди, которые указывают, какова «дорогая» обработка исключений, пренебрегают всеми дополнительными
if
утверждениями, подобными приведенным выше, которые вам необходимо добавить, если вы не использовать исключения.Хотя этот шаблон встречается чаще всего при обработке ошибок или других «исключительных условий», на мой взгляд, если вы начнете видеть шаблонный код, подобный этому, в других обстоятельствах, у вас будет довольно хороший аргумент для использования исключений. В зависимости от ситуации и реализации, я вижу, как исключения используются в автомате состояний, потому что у вас есть два ортогональных потока управления: один изменяет состояние, а другой - для событий, происходящих внутри состояний.
Однако такие ситуации редки, и если вы собираетесь сделать исключение (каламбур) из правила, вам лучше быть готовым показать его превосходство над другими решениями. Отклонение без такого оправдания справедливо называется анти-паттерном.
источник
В Python исключения используются для завершения генератора и итерации. Python имеет очень эффективные блоки try / кроме, но на самом деле вызов исключения имеет некоторые накладные расходы.
Из-за отсутствия многоуровневых разрывов или оператора goto в Python я иногда использовал исключения:
источник
return
вместоgoto
.try:
блоки в 1 или 2 строки, пожалуйста, никогдаtry: # Do lots of stuff
.Давайте нарисуем такое исключение:
Алгоритм ищет рекурсивно, пока что-то не найдено. Поэтому, возвращаясь из рекурсии, нужно проверить результат на предмет обнаружения и затем вернуться, в противном случае продолжить. И это неоднократно возвращалось с некоторой глубины рекурсии.
Помимо необходимости в дополнительном логическом значении
found
(для упаковки в классе, где в противном случае, возможно, было бы возвращено только int), и для глубины рекурсии происходит то же самое.Такое раскручивание стека вызовов как раз и является исключением. Так что, мне кажется, это не гото-подобные, более быстрые и подходящие средства кодирования. Не нужно, редкое использование, может быть, плохой стиль, но в точку. Сравнимо с операцией разрезания Prolog.
источник
Программирование - это работа
Я думаю, что самый простой способ ответить на это, это понять прогресс, достигнутый ООП за эти годы. Все, что делается в ООП (и в большинстве случаев в парадигмах программирования), смоделировано вокруг необходимости работы .
Каждый раз, когда вызывается метод, вызывающий абонент говорит: «Я не знаю, как выполнить эту работу, но вы знаете, как, поэтому вы делаете это для меня».
Это представляло сложность: что происходит, когда вызываемый метод обычно знает, как выполнять работу, но не всегда? Нам нужен был способ общения: «Я хотел помочь тебе, я действительно помог, но я просто не могу этого сделать».
Ранняя методология для сообщения об этом состояла в том, чтобы просто возвращать «мусорное» значение. Возможно, вы ожидаете положительное целое число, поэтому вызываемый метод возвращает отрицательное число. Еще один способ сделать это - установить где-нибудь значение ошибки. К сожалению, оба способа привели к тому, что код « дай мне проверить здесь, чтобы убедиться, что все есть», кошерный . Когда все усложняется, эта система разваливается.
Исключительная аналогия
Допустим, у вас есть плотник, сантехник и электрик. Вы хотите, чтобы сантехник починил вашу раковину, поэтому он на это смотрит. Это не очень полезно, если он говорит только вам: «Извините, я не могу это исправить. Он сломан». Черт, еще хуже, если бы он посмотрел, ушел и отправил вам письмо, в котором говорилось, что он не может это исправить. Теперь вам нужно проверить свою почту, прежде чем вы даже узнаете, что он не сделал то, что вы хотели.
Что бы вы предпочли, так это попросить его сказать: «Послушайте, я не смог это исправить, потому что, похоже, ваш насос не работает»
С помощью этой информации вы можете сделать вывод, что хотите, чтобы электрик посмотрел на проблему. Возможно, электрик найдет что-то связанное с плотником, и вам нужно будет починить его.
Черт, вы можете даже не знать, что вам нужен электрик, вы можете не знать, кто вам нужен. Вы просто менеджер среднего звена в сфере ремонта дома, и ваше внимание сосредоточено на сантехнике. Итак, вы говорите, что вы босс о проблеме, а затем он говорит электрику, чтобы исправить это.
Это то, что являются исключениями моделирования: сложные режимы отказов в развязанном виде. Сантехнику не нужно знать об электрике - ему даже не нужно знать, что кто-то в цепи может решить проблему. Он просто сообщает о проблеме, с которой столкнулся.
Так что ... анти-паттерн?
Итак, понимание точки исключений является первым шагом. Следующее - понять, что такое анти-паттерн.
Чтобы считаться анти-паттерном, необходимо
Первый пункт легко соблюдается - система работала, верно?
Второй момент более липкий. Основная причина использования исключений в качестве нормального потока управления плохая, потому что это не является их целью. Любая конкретная часть функциональности в программе должна иметь относительно ясную цель, и совместное использование этой цели приводит к ненужной путанице.
Но это не окончательный вред. Это плохой способ сделать что-то, и странный, но анти-паттерн? Нет ... Просто ... странно.
источник