Некоторое время я размышлял над этой проблемой и постоянно нахожу предостережения и противоречия, поэтому надеюсь, что кто-нибудь сможет сделать вывод о следующем:
Избранные исключения по кодам ошибок
Насколько мне известно, после четырех лет работы в отрасли, чтения книг, блогов и т. Д. В настоящее время наилучшей практикой для обработки ошибок является создание исключений, а не возвращение кодов ошибок (не обязательно код ошибки, но тип, представляющий ошибку).
Но - мне кажется, это противоречит ...
Кодирование к интерфейсам, а не реализации
Мы кодируем интерфейсы или абстракции, чтобы уменьшить связь. Мы не знаем или не хотим знать конкретный тип и реализацию интерфейса. Так как же мы можем знать, какие исключения мы должны искать? Реализация может выдать 10 разных исключений или не выдать ни одного. Когда мы ловим исключение, конечно, мы делаем предположения о реализации?
Если только - интерфейс имеет ...
Спецификации исключений
Некоторые языки позволяют разработчикам утверждать, что определенные методы генерируют определенные исключения (например, Java использует throws
ключевое слово.) С точки зрения вызывающего кода это выглядит хорошо - мы точно знаем, какие исключения нам может понадобиться перехватить.
Но - это, кажется, предполагает ...
Утечка абстракции
Почему интерфейс должен указывать, какие исключения могут быть выданы? Что, если реализация не должна вызывать исключение или должна генерировать другие исключения? На уровне интерфейса нет способа узнать, какие исключения реализация может захотеть выдать.
Так...
Заключить
Почему исключения предпочтительны, когда они (на мой взгляд) противоречат лучшим практикам программного обеспечения? И, если коды ошибок настолько плохи (и мне не нужно продаваться с пороками кодов ошибок), есть ли другая альтернатива? Каково текущее (или скорое) состояние в области обработки ошибок, которое соответствует требованиям передового опыта, изложенным выше, но не зависит от вызова кода, проверяющего возвращаемое значение кодов ошибок?
Ответы:
Прежде всего, я бы не согласился с этим утверждением:
Это не всегда так: например, взгляните на Objective-C (со структурой Foundation). Там NSError является предпочтительным способом обработки ошибок, несмотря на существование того, что разработчик Java назвал бы истинными исключениями: @try, @catch, @throw, класс NSException и т. Д.
Однако это правда, что многие интерфейсы пропускают свои абстракции с исключениями. Я считаю, что это не вина "исключительного" стиля распространения / обработки ошибок. В общем, я считаю, что лучший совет по обработке ошибок заключается в следующем:
Обработать ошибку / исключение на минимально возможном уровне, период
Я думаю, что если придерживаться этого эмпирического правила, количество «утечек» из абстракций может быть очень ограниченным и ограниченным.
Относительно того, должны ли исключения, сгенерированные методом, быть частью его объявления, я полагаю, что они должны: они являются частью контракта, определенного этим интерфейсом: этот метод делает A, или терпит неудачу с B или C.
Например, если класс является синтаксическим анализатором XML, часть его дизайна должна указывать на то, что предоставленный файл XML является просто неправильным. В Java вы обычно делаете это, объявляя исключения, которые ожидаете встретить, и добавляя их в
throws
часть объявления метода. С другой стороны, если один из алгоритмов синтаксического анализа потерпел неудачу, нет никакой причины пропускать это исключение выше необработанным.Все сводится к одному: хороший дизайн интерфейса. Если вы разрабатываете свой интерфейс достаточно хорошо, никакие исключения не должны преследовать вас. В противном случае вас будут беспокоить не только исключения.
Кроме того, я думаю, что создатели Java имели очень веские причины для безопасности включать исключения в объявление / определение метода.
И последнее: некоторые языки, например, Eiffel, имеют другие механизмы для обработки ошибок и просто не включают в себя возможности выбрасывания. Там «исключение» сортировки автоматически возникает, когда постусловие для подпрограммы не выполняется.
источник
goto
очень разные. Например, исключения всегда идут в одном и том же направлении - вниз по стеку вызовов. И, во-вторых, защита себя от неожиданных исключений - та же самая практика, что и DRY - например, в C ++, если вы используете RAII для обеспечения очистки, то она обеспечивает очистку во всех случаях, не только в исключениях, но и во всем обычном потоке управления. Это бесконечно более надежно.try/finally
выполняет что-то похожее. Когда вы должным образом гарантируете очистку, вам не нужно рассматривать исключения как особый случай.Я просто хотел бы отметить, что исключения и коды ошибок - не единственный способ справиться с ошибками и альтернативными путями кода.
Вдобавок ко всему, у вас может быть такой подход, как у Haskell, когда об ошибках можно сигнализировать с помощью абстрактных типов данных с несколькими конструкторами (например, различимые перечисления или нулевые указатели, но безопасные с точки зрения типов и с возможностью добавления синтаксиса). сахар или вспомогательные функции, чтобы поток кода выглядел хорошо).
operationThatMightfail - это функция, которая возвращает значение, заключенное в Maybe. Он работает как обнуляемый указатель, но нотация do гарантирует, что все это будет равно нулю, если любой из a, b или c потерпит неудачу. (и компилятор защищает вас от случайного исключения NullPointerException)
Другой возможностью является передача объекта обработчика ошибок в качестве дополнительного аргумента каждой вызываемой вами функции. Этот обработчик ошибок имеет метод для каждого возможного «исключения», которое может быть передано функцией, которой вы его передаете, и может использоваться этой функцией для обработки исключений там, где они возникают, без необходимости перематывать стек через исключения.
Common LISP делает это и делает это возможным благодаря наличию синтетической поддержки (неявные аргументы) и наличию встроенных функций, следующих этому протоколу.
источник
Maybe
есть.Да, исключения могут вызвать утечку абстракций. Но неужели коды ошибок не хуже в этом отношении?
Один из способов решения этой проблемы состоит в том, чтобы интерфейс точно указывал, какие исключения могут создаваться при каких обстоятельствах, и объявляет, что реализации должны отображать свою внутреннюю модель исключений в эту спецификацию, перехватывая, преобразовывая и повторно выбрасывая исключения, если это необходимо. Если вы хотите «префект» интерфейс, это путь.
На практике обычно достаточно указать исключения, которые логически являются частью интерфейса и которые клиент может захотеть перехватить и что-то предпринять. Обычно понимают, что могут быть и другие исключения, когда происходят ошибки низкого уровня или появляется ошибка, и которые клиент может обрабатывать, как правило, только показывая сообщение об ошибке и / или закрывая приложение. По крайней мере, исключение может содержать информацию, которая помогает диагностировать проблему.
Фактически, с кодами ошибок, в конечном итоге происходит то же самое, просто в более скрытой форме, и с гораздо большей вероятностью потери информации и того, что приложение окажется в несогласованном состоянии.
источник
getSQLState
(общий) иgetErrorCode
(зависит от поставщика). Вот если бы у него были правильные подклассы ...Здесь много хороших вещей, я просто хотел бы добавить, что мы все должны быть осторожны с кодом, который использует исключения как часть нормального потока управления. Иногда люди попадают в эту ловушку, где все, что не является обычным случаем, становится исключением. Я даже видел исключение, используемое в качестве условия завершения цикла.
Исключения означают «что-то, с чем я не могу справиться, произошло, нужно пойти к кому-то другому, чтобы выяснить, что делать». Пользователь, вводящий недопустимый ввод, не является исключением (это должно быть обработано вводом путем повторного запроса и т. Д.).
Я видел еще один вырожденный случай использования исключений - люди, чей первый ответ - «выбросить исключение». Это почти всегда делается без написания catch (эмпирическое правило: сначала пишите catch, а затем оператор throw). В больших приложениях это становится проблематичным, когда неисследованное исключение всплывает из нижних областей и взрывает программу.
Я не исключение, но они кажутся одиночками нескольких лет назад: используются слишком часто и неуместно. Они идеально подходят для предполагаемого использования, но этот случай не так широк, как некоторые думают.
источник
Нет. Спецификации исключений находятся в том же сегменте, что и типы возврата и аргумента - они являются частью интерфейса. Если вы не можете соответствовать этой спецификации, не используйте интерфейс. Если ты никогда не бросаешь, это нормально. Нет ничего плохого в определении исключений в интерфейсе.
Коды ошибок выше плохих. Они ужасны Вы должны вручную помнить, чтобы проверять и распространять их, каждый раз, для каждого звонка. Для начала, это нарушает DRY и сильно взрывает ваш код обработки ошибок. Это повторение представляет собой гораздо большую проблему, чем любая другая, с которой сталкиваются исключения. Вы никогда не можете молча игнорировать исключение, но люди могут и молчаливо игнорировать коды возврата - определенно плохая вещь.
источник
Ну, обработка исключений может иметь собственную реализацию интерфейса. В зависимости от типа сгенерированного исключения выполните необходимые шаги.
Решение вашей проблемы дизайна состоит в том, чтобы иметь две реализации интерфейса / абстракции. Один для функциональности, а другой для обработки исключений. И в зависимости от типа пойманного исключения, вызовите соответствующий класс типа исключения.
Реализация кодов ошибок является традиционным способом обработки исключений. Это похоже на использование строки против строителя строк.
источник
Исключения IM-very-HO должны оцениваться в каждом конкретном случае, потому что, нарушая поток управления, они увеличивают фактическую и воспринимаемую сложность вашего кода, во многих случаях излишне. Оставляя в стороне обсуждение, связанное с выбрасыванием исключений внутри ваших функций - что может фактически улучшить ваш поток управления, если нужно посмотреть на выброс исключений через границы вызовов, рассмотрите следующее:
Разрешение вызываемому нарушить ваш поток управления может не принести никакой реальной выгоды, и не может быть никакого значимого способа справиться с этим исключением. Для прямого примера: если кто-то реализует шаблон Observable (на языке, таком как C #, где у вас есть события повсюду, а
throws
в определении нет явного ), нет реальной причины позволить Observer нарушать ваш поток управления в случае сбоя, и нет осмысленного способа разобраться со своими вещами (конечно, хороший сосед не должен бросать при наблюдении, но никто не идеален).Наблюдение выше может быть распространено на любой слабо связанный интерфейс (как вы указали); Я думаю, что на самом деле является нормой, что после ползания 3-6 стековых фреймов неперехваченное исключение может оказаться в разделе кода, который либо:
Учитывая вышесказанное, украшать интерфейсы
throws
семантикой - это лишь незначительное функциональное преимущество, потому что многие вызывающие абоненты по контрактам на интерфейсе будут заботиться только о том, что вы потерпели неудачу, а не почему.Я бы сказал, что тогда это становится вопросом вкуса и удобства: ваша основная задача заключается в изящном восстановлении вашего состояния как в вызывающей, так и в вызываемой стороне после «исключения», поэтому, если у вас есть большой опыт перемещения кодов ошибок вокруг из опыта C), или если вы работаете в среде, где исключения могут стать злом (C ++), я не верю, что разбрасывать вещи настолько важно для хорошего, чистого ООП, что вы не можете положиться на свой старый шаблоны, если вам неудобно с этим. Особенно если это приводит к взлому SoC.
С теоретической точки зрения, я думаю, что SoC-кошерный способ обработки исключений может быть получен непосредственно из наблюдения, что в большинстве случаев прямой вызывающий абонент заботится только о том, что вы потерпели неудачу, а не почему. Бросок вызываемого абонента, кто-то очень близко выше (2-3 кадра) ловит версию с обновлением, и фактическое исключение всегда погружается в специализированный обработчик ошибок (даже если только трассировка) - это то, где AOP пригодится, потому что эти обработчики скорее всего будет горизонтальным.
источник
Оба должны сосуществовать.
Вернуть код ошибки, когда вы ожидаете определенного поведения.
Возвратите исключение, если вы не ожидали какого-либо поведения.
Коды ошибок обычно связаны с одним сообщением, когда тип исключения сохраняется, но сообщение может отличаться
Исключение имеет трассировку стека, а код ошибки - нет. Я не использую коды ошибок для отладки сломанной системы.
Это может быть характерно для JAVA, но когда я объявляю свои интерфейсы, я не указываю, какие исключения могут быть вызваны реализацией этого интерфейса, это просто не имеет смысла.
Это полностью зависит от вас. Вы можете попытаться поймать очень специфический тип исключения, а затем поймать более общее
Exception
. Почему бы не позволить исключению распространяться вверх по стеку, а затем обрабатывать его? В качестве альтернативы вы можете взглянуть на программирование аспектов, где обработка исключений становится «подключаемым» аспектом.Я не понимаю, почему это проблема для вас. Да, у вас может быть одна реализация, которая никогда не приводит к сбою или выбрасывает исключения, и у вас может быть другая реализация, которая постоянно дает сбой и выдает исключение. Если это так, то не указывайте никаких исключений в интерфейсе, и ваша проблема решена.
Изменится ли что-нибудь, если вместо исключения ваша реализация вернет объект результата? Этот объект будет содержать результаты ваших действий, а также любые ошибки / сбои, если таковые имеются. Затем вы можете опросить этот объект.
источник
По моему опыту, код, который получает ошибку (будь то с помощью исключения, кода ошибки или чего-либо еще), обычно не заботится о точной причине ошибки - он будет реагировать таким же образом на любой сбой, за исключением возможного сообщения о неисправности. ошибка (будь то диалог об ошибке или какой-то журнал); и это сообщение будет сделано ортогонально к коду, который вызвал ошибочную процедуру. Например, этот код может передавать ошибку другому фрагменту кода, который знает, как сообщать о конкретных ошибках (например, форматировать строку сообщения), возможно, прикрепляя некоторую контекстную информацию.
Конечно, в некоторых случаях это необходимо приложить определенную семантику к ошибкам и срабатывает по- разному , на основании которого произошла ошибка. Такие случаи должны быть документированы в спецификации интерфейса. Тем не менее, интерфейс все еще может оставить за собой право выдавать другие исключения без какого-либо конкретного значения.
источник
Я обнаружил, что исключения позволяют писать более структурированный и лаконичный код для сообщения и обработки ошибок: использование кодов ошибок требует проверки возвращаемых значений после каждого вызова и решения, что делать в случае неожиданного результата.
С другой стороны, я согласен, что исключения раскрывают детали реализации, которые должны быть скрыты в коде, вызывающем интерфейс. Поскольку априори невозможно определить, какой фрагмент кода может генерировать какие исключения (если они не объявлены в сигнатуре метода, как в Java), с помощью исключений мы вводим очень сложные неявные зависимости между различными частями кода, что против принципа минимизации зависимостей.
Подводя итог:
источник
catch Exception
или дажеThrowable
, или эквивалент).Право предвзятый Либо.
Это не может быть проигнорировано, это должно быть обработано, это полностью прозрачно. И если вы используете правильный левый тип ошибки, он передает всю ту же информацию, что и исключение Java.
Даунсайд? Код с правильной обработкой ошибок выглядит отвратительно (верно для всех механизмов).
источник