Исключения и коды ошибок: правильное их смешивание - PullRequest
25 голосов
/ 27 апреля 2011

Я занимаюсь разработкой библиотеки для общения с C ++ ключом.Библиотека предоставит унифицированный интерфейс для связи с различными удаленными ключами исполнения кода, такими как SenseLock, KEYLOK, Guardant Code.

Ключи основаны на технологии смарт-карт и имеют внутреннюю файловую систему и оперативную память.

Типичная рабочая процедура включает в себя (1) перечисление ключей, подключенных к USB-портам, (2) подключение к выбранному ключу, (3) выполнение именованного модуля, передающего входные данные и собирающего выходные данные.

Ну, тривиально, что все эти этапы могут привести к ошибке.Может быть много случаев, но наиболее общими являются:

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

?- Я пока не знаю, считается ли дело фатальным.

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

Вопросы:

  1. Заменяют ли исключения коды ошибок полностью или, может быть, мне нужно использовать их только для "смертельных случаев"?
  2. Является ли смешивание двух парадигм (исключений и кодов ошибок) хорошей идеей?
  3. Является ли хорошей идеей предоставить пользователю две концепции?
  4. Существуют ли хорошие примеры исключений и кодов ошибок?Концепция смешивания?
  5. Как бы вы это реализовали?

Обновление 1.

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

Ответы [ 11 ]

17 голосов
/ 27 апреля 2011

Если мы говорим о внутренней политике обработки ошибок приложения / модуля C ++, то мое личное мнение таково:

Q: Исключают ли исключения коды ошибок полностью или, возможно, мне нужно использоватьих только для "смертельных случаев"?

A: Они делают.Исключения всегда лучше для функции C ++, чем возврат кодов ошибок.Причина объясняется ниже.

Q: Является ли смешивание двух парадигм (исключений и кодов ошибок) хорошей идеей?

A: НетСмешивание является уродливым и делает обработку ошибок несовместимой.Когда я вынужден использовать API, возвращающий ошибки (например, Win32 API, POSIX и т. Д.), Я использую оболочки, генерирующие исключения.

Q: Является ли хорошей идеей предоставить пользователю две концепции?

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

Q: Есть ли хорошие примерыКонцепция смешивания исключений и кодов ошибок?

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

Q: Как бы выреализовать это?

A: Я бы использовал только исключения.Мой путь возвращается только в случае успеха.Практика возврата ошибок сильно путает код с ветвями проверки ошибок или, что еще хуже, проверка состояния ошибок отсутствует, и поэтому статус ошибок просто игнорируется, что делает код полным скрытых ошибок, которые трудно обнаружить.Исключения делают обработку ошибок изолированной.Если вам нужно обработать какую-то ошибку на месте, это обычно означает, что это вовсе не ошибка, а просто какое-то законное событие, о котором можно сообщить при успешном возврате с указанием определенного состояния (по возвращаемому значению или иным образом).Если вам действительно нужно проверить, произошла ли какая-либо ошибка локально (не из корневого блока try / catch), вы можете попробовать / перехватить локально, поэтому использование только исключений на самом деле не ограничивает ваши возможности.

Важное примечание:

Для каждой конкретной ситуации очень важно правильно определить, что является ошибкой, а что нет (для лучшего удобства использования).

Например, у нас есть функция, котораяпоказывает диалог ввода и текст возврата, введенный пользователем, и если пользователь может отменить ввод, то событие отмены будет успешным - не ошибка (но при возврате пользователь должен как-то указать, что ввод был отменен), но нехватка ресурсов (таких как память или GDIобъекты или что-то) или что-то вроде отсутствия монитора, чтобы показать диалоговое окно действительно ошибка.

В целом:

Исключения являются более естественным механизмом обработки ошибок дляЯзык С ++Поэтому использование исключений - хорошая идея, если вы разрабатываете приложение или библиотеку C ++ для использования только приложением C ++ (не приложением C и т. Д.).Возврат ошибок - более переносимый подход - вы можете возвращать коды ошибок в приложения, написанные на любом языке программирования и даже работающие на другом компьютере.Конечно, почти всегда запросы ОС сообщают о своем состоянии с помощью кодов ошибок (потому что естественно сделать их независимыми от языка).И по этой причине вам приходится иметь дело с кодами ошибок в повседневном программировании. НО Политика планирования ошибок IMO приложения C ++, основанная на кодах ошибок, просто напрашивается на неприятности - приложение превращается в совершенно нечитаемый беспорядок.IMO лучший способ справиться с кодами состояния в приложении C ++ - это использовать функции / классы / методы-оболочки C ++ для вызова функций, возвращающих ошибки, а если возвращается ошибка - генерировать исключение (с информацией о состоянии, встроенной в класс исключения).

Некоторые важные замечания и предостережения:

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

Рассмотрите этот код:

SomeType* p = new SomeType;

some_list.push_back(p);
/* later element of some_list have to be delete-ed
   after removing them from this list */

Это типичная потенциальная утечка памяти - если push_back генерирует исключение, то динамически размещаемый и создаваемый объект SomeType просачивается.

Безопасный вариант исключения является следующим:

std::auto_ptr<SomeType> pa( new SomeType );

some_list.push_back(pa.get());
pa.release();
/* later elements of some_list have to be delete-ed
   after removing them from this list */

Или:

boost::shared_ptr<SomeType> pa( new SomeType );

some_list.push_back(pa);
/* some_list is list of boost::shared_ptr<SomeType>
   so everything is delete-ed automatically */

Если вы используете стандартные шаблоны C ++, распределители и т. Д., Вы либо пишете безопасный код исключения (если вы пытаетесь / перехватываете каждый отдельный вызов STL, код становится беспорядочным), либо оставляете код полным потенциальных утечек ресурсов (то естьк сожалению бывает очень часто).Истинное приложение C ++ должно быть безопасным для исключений.

13 голосов
/ 27 апреля 2011

Есть ли хорошие примеры концепции смешивания исключений и кодов ошибок?

Да, boost.asio - это вездесущая библиотека, используемая для сетевой и последовательной связи в C ++, и почти каждая функция поставляется в двух версиях: исключение и возврат ошибок.

Например, iterator resolve(const query&) выдает boost::system::system_error при ошибке, а iterator resolve(const query&, boost::system::error_code & ec) изменяет ссылочный аргумент ec.

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

8 голосов
/ 09 июня 2011
  • Используйте коды ошибок , где приложение обычно продолжит выполнение с этой точки.
  • Используйте исключения , где приложение будетобычно не продолжает выполнение с этой точки.

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

DeleteFile(filename);

Иногда мне все равно, если это не удастся (например, с ошибкой «файл не найден») - я просто хочу убедиться, что она удалена.Таким образом, я могу игнорировать возвращенный код ошибки, не прибегая к его использованию.

С другой стороны:

CreateDirectory(path);

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

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

7 голосов
/ 27 апреля 2011

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

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

Естественно, здесь есть большая серая зона. Что часто, а что далеко?

Я бы не советовал вам указывать обе альтернативы, так как это обычно сбивает с толку.

4 голосов
/ 03 июня 2011

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

Вы должны опустить здесь как можно больше информации и убедиться, что вашей процедуре проверки ВСЕГДА требуется столько же времени для проверки карты, чтобы атака по времени была невозможна.

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

4 голосов
/ 27 апреля 2011

Должен признать, что я ценю то, как вы классифицируете ошибки.

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

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

Самым простым является "необязательный" синтаксис.Если вы ищете объект в коллекции, вы можете его не найти.Здесь есть одна причина: ее нет в коллекции.Таким образом, код ошибки определенно ложный.Вместо этого можно использовать:

  • указатель (совместно используемый, если вы хотите разделить владение)
  • объект, похожий на указатель (например, итератор)
  • значение-как обнуляемый объект (спасибо boost::optional<> здесь)

Когда все более сложно, я склонен использовать «альтернативный» тип.На самом деле это идея типа Either в haskell.Либо вы возвращаете то, о чем просил пользователь, либо вы указываете, почему вы его не вернули.boost::variant<> и сопровождающий boost::static_visitor<> здесь играют неплохо (в функциональном программировании это делается посредством деконструкции объекта при сопоставлении с образцом).

Основная идея заключается в том, что код ошибки можно игнорировать , однакоесли вы возвращаете объект, который является результатом функции XOR и кода ошибки, то он не может быть отброшен без предупреждения (boost::variant<> и boost::optional<> действительно хороши здесь).

3 голосов
/ 05 июня 2011

Меня не интересует, насколько это хорошая идея, но недавно я работал над проектом, в котором они не могли разбрасываться вокруг исключений, но не доверяли кодам ошибок. Таким образом, они возвращают Error<T>, где T - это любой тип кода ошибки, который они вернули бы (обычно и в некотором роде, иногда в строке). Если результат вышел из области видимости без проверки и произошла ошибка, возникнет исключение. Поэтому, если вы знали, что ничего не можете сделать, вы можете проигнорировать ошибку и сгенерировать исключение, как и ожидалось, но если вы можете что-то сделать, вы можете явно проверить результат.

Это была интересная смесь.

Этот вопрос продолжает появляться в верхней части активного списка, поэтому я решил немного подробнее рассказать о том, как это работает. Класс Error<T> хранил исключение, стертое по типу, поэтому его использование не заставляло использовать определенную иерархию исключений или что-либо подобное, и каждый отдельный метод мог выдавать столько исключений, сколько ему хотелось. Вы можете даже бросить int с или что-то еще; почти что угодно с конструктором копирования.

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

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

Error<int> result = some_function();
do_something_independant_of_some_function();
if(result)
    do_something_else_only_if_some_function_succeeds();

Проверка if(result) гарантирует, что система ошибок перечисляет эту ошибку как обработанную, поэтому у result нет причины выдавать свое сохраненное исключение при уничтожении. Но если do_something_independant_of_some_function бросит, result будет уничтожен до того, как достигнет этой проверки. Это приводит к тому, что деструктор выдает второе исключение, и программа просто сдается и уходит домой. Этого чрезвычайно легко избежать [всегда проверяйте результат функции, прежде чем делать что-либо еще], но все же рискованно.

3 голосов
/ 27 апреля 2011

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

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

Является ли смешивание двух парадигм (исключений и кодов ошибок) хорошей идеей?

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

Это хорошая идея, чтобы предоставить пользователю две концепции?

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

Как бы вы это реализовали?

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

3 голосов
/ 27 апреля 2011

Как я это делаю, у меня есть класс Exception (только объект исключения, который вы выбрасываете), который состоит из строкового сообщения и перечисления кода ошибки и некоторых изящных конструкторов.попробуйте catch block, когда восстановление будет значимым и возможным.Обычно этот блок будет пытаться обработать только один или два из перечисленных кодов ошибок при отбрасывании остальных.На верхнем уровне все мое приложение запускается в блоке try catch, который регистрирует все необработанные не фатальные ошибки, в то время как оно завершается с окном сообщения, если ошибка фатальная.ошибка, но мне нравится, когда все это в одном месте.

2 голосов
/ 08 июня 2011

Храните исключения локально для вашей библиотеки / промежуточного программного обеспечения для вашей логики.

Вместо этого используйте коды ошибок для связи с клиентом.

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