Теория обработки ошибок?

Большинство рекомендаций по обработке ошибок сводятся к нескольким советам и трюкам (например, этот пост). Эти подсказки полезны, но я думаю, что они не отвечают на все вопросы. Я чувствую, что должен разработать свое приложение в соответствии с определенной философией, школой мысли, которая обеспечивает прочную основу для развития. Существует ли такая теория в отношении обработки ошибок?

Вот несколько практических вопросов:

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

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

PS: это общий вопрос, но ответы на С++ тоже приветствуются (С++ - это мой основной язык программирования для работы).

Ответ 1

Пару лет назад я думал точно о том же вопросе:)

После поиска и чтения нескольких вещей, я думаю, что самая интересная ссылка, которую я нашел, была Шаблоны для генерации, обработки и управления ошибками от Andy Лонгшоу и Эоин Вудс. Это короткая и систематическая попытка охватить основные идиомы, которые вы упомянули, и некоторые другие.

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

Ответ 2

Записывает то, что должно быть сделано в коде приложения? Или это ok, чтобы выполнить некоторую регистрацию из библиотеки код.

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

Ответ 3

Введение

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

Для меня было чрезвычайно полезно рассмотреть два основных типа ошибок:

  • Ошибки, которые никогда не должны происходить, и, как правило, из-за ошибки в коде.

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

Способ обработки ошибки в значительной степени зависит от типа ошибки.

Различные контексты, которые влияют на обработку ошибок, следующие:

  • Код приложения

  • Код библиотеки

Обработка ошибок в коде библиотеки несколько отличается от обработки в коде приложения.

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

Типы ошибок

Ошибки программирования - ошибки - и другие ошибки, которые никогда не должны происходить

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

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

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

Серверные приложения

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

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

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

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

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

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

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

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

Библиотеки и приложения

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

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

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

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

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

Практические вопросы

Теперь, когда у нас есть философия, примените ее к практическим вопросам, которые вы упомянули.

  • Как решить, следует ли обрабатывать ошибку локально или распространяться на код более высокого уровня?

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

  • Как решить, как регистрировать ошибку или показывать ее как сообщение об ошибке пользователю?

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

  • Записывает ли что-то, что должно быть сделано только в коде приложения? Или это нормально делать некоторые записи из библиотечного кода.

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

  • В случае исключений, где вы обычно их поймаете? В коде низкого уровня или более высокого уровня?

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

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

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

  • Имеет ли смысл создавать список кодов ошибок? Или это старомодно в наши дни?

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

Ответ 4

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

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


Введение

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

Автор не может надеяться понять, как эта библиотека будет использоваться, и, таким образом, избегать стратегий, которые затрудняют интеграцию. Самый вопиющий дефект будет опираться на глобально разделяемое состояние; thread-local shared state также может быть кошмаром для взаимодействия с сопрограммами/зелеными потоками. Использование таких сопрограмм и потоков также подчеркивает, что синхронизация лучше всего оставить пользователю, в однопоточном коде это будет означать ничто (лучшая производительность), в то время как в сопрограммах и зеленых потоках пользователь лучше всего подходит для реализации (или использования существующих реализации) выделенных механизмов синхронизации.

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


Logging

Существует много способов записи сообщений:

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

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

  • чтобы обеспечить 2 крючка: один, чтобы решить, следует ли регистрироваться или нет, и один для фактического журнала (форматированное сообщение и последний вызов вызываются только тогда, когда клиент решил зарегистрироваться)
  • чтобы предоставить, помимо сообщения: серьезность (или уровень), имя файла, имя строки и функции, если с открытым исходным кодом или иначе логический модуль (если несколько)
  • to, по умолчанию, пишите в stdout и stderr (в зависимости от степени серьезности), пока клиент явно не укажет log

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

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

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

Записывает ли что-то, что должно быть сделано только в коде приложения? Или это нормально делать некоторые записи из библиотечного кода.

Код приложения должен определять политику. Независимо от того, работает ли библиотека или нет, зависит ли она от нее.


Продолжение после ошибки?

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

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

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

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


Сообщить об ошибке

Возникновение ошибки означает, что контракт функции/интерфейса не может быть выполнен. Это имеет несколько последствий:

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

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

ErrorStatus_t doit(Input const* input, Output* output);

Я знаю две схемы, которые требуют явного действия для клиентской части:

  • исключения
  • типы результатов (optional<T>, either<T, U>,...)

Первое хорошо известно, последнее очень часто используется в функциональных языках и было введено в С++ 11 под видом std::future<T>, хотя существуют другие реализации.

Советую предпочесть последнее, когда это возможно, как это легче понять, но вернуться к исключениям, когда не ожидается никакого результата. Контраст:

Option<Value&> find(Key const&);

void updateName(Client::Id id, Client::Name name);

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

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

Option<Value&> compute(RepositoryInterface&, Details...);

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

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

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

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


Использование ошибки

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

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

Заметным исключением являются, конечно же, типы результатов: boost::variant<Output, Error0, Error1, ...> предоставляет проверенный с помощью компилятора исчерпывающий список известных режимов отказа... хотя функция, возвращающая этот тип, все равно может бросать.

Как решить, как регистрировать ошибку или показывать ее как сообщение об ошибке пользователю?

Пользователь всегда должен быть предупрежден, когда его заказ не может быть выполнен, однако должно быть показано удобное для пользователя (понятное) сообщение. Если это возможно, также должны быть представлены советы или рекомендации. Подробности для расследования команд.


Восстановление из-за ошибки?

Наконец, но, конечно, не в последнюю очередь, приходит поистине страшная часть об ошибках: восстановление.

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

В реальном мире все не так просто. Простой пример отмены отправки сообщений по электронной почте: слишком поздно. Протоколы могут существовать в зависимости от вашего домена приложения, но это не обсуждается. Первым шагом, однако, является возможность восстановления нормального состояния в памяти; и это далеко не просто на большинстве языков (и STM может делать это только сегодня).

Прежде всего, иллюстрация проблемы:

void update(Client& client, Client::Name name, Client::Address address) {
    client.update(std::move(name));
    client.update(std::move(address)); // Throws
}

Теперь, после обновления адреса не удалось, я остался с половиной обновленной client. Что я могу сделать?

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

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

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

Как часто, единственный способ выиграть - не играть.


Возможное решение: транзакции

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

Either<Output, Error> doit(Input const&);

// or

Output doit(Input const&); // throw in case of error

Транзакция не изменяет никакого внешнего состояния, поэтому, если она не создает результат:

  • внешний мир не изменился (ничего не откат)
  • нет частичного результата для наблюдения

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

Как решить, следует ли обрабатывать ошибку локально или распространяться на код более высокого уровня?

В случае исключений, где вы обычно их поймаете? В коде низкого уровня или более высокого уровня?

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


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

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

Ответ 5

Как решить, следует ли обрабатывать ошибку локально или распространяться на код более высокого уровня?

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

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

Записывает ли что-то, что должно быть сделано только в коде приложения? Или это нормально делать некоторые записи из кода библиотеки.

Я не вижу причин для регистрации ошибок в dll. Это должно только порождать ошибки. Конечно, может быть определенная причина. В нашей компании dll регистрирует информацию о процессе (не только ошибки)

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

В контроллере.

Изменить: Мне нужно немного это объяснить, если вы не знакомы с MVC. Model View Controller - это шаблон проектирования. В модели вы разрабатываете прикладную логику. В режиме просмотра вы показываете контент пользователю. В контроллере вы получаете пользовательские события и вызываете "Модель" для соответствующей функции, затем вызываете "Вид" для отображения результата для пользователя.

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

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

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

Имеет ли смысл создавать список кодов ошибок? Или это старомодное в наши дни?

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

If (error.Message == "User Login Failed")
{
   //do something.
}

If (error.Code == "102")
{
   //do something.
}

Какой из них вы предпочитаете?

И в наши дни есть другой способ для кодов ошибок:

If (error.Code == "LOGIN_ERROR_102") // wrong password
{
   //do something.
}

Другие могут быть: LOGIN_ERROR_103 (например, это истек пользователь) и т.д.

Этот человек также читается человеком.

Ответ 6

Мой взгляд на регистрацию (или другие действия) из кода библиотеки НИКОГДА.

Библиотека не должна налагать политику на пользователя, и пользователь может иметь ПРЕДНАЗНАЧЕНУ ошибку. Возможно, программа намеренно запрашивала конкретную ошибку, ожидая ее прибытия, чтобы проверить какое-то условие. Запись этой ошибки будет вводить в заблуждение.

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

Ответ 7

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

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

  • Почему бы и нет?

  • см. 1.

  • см. 1.

  • Вам нужно, чтобы все было просто, или вы пожалеете об этом. Более важным для обработки ошибок во время выполнения является тестирование, чтобы избежать их.

  • Мне нравится говорить, что лучше централизовать или не централизовать. В некоторых случаях это может иметь большой смысл, но быть пустым временем в других. Для того, что является загружаемым lib/модулем, который может иметь ошибки, связанные с данными (вывоз мусора, вывоз мусора), это делает тонны смысла. Для более общей обработки ошибок или катастрофических ошибок меньше.

Ответ 8

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

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

Хороший взгляд на эту тему представлен А. Александреску в его речи систематическая обработка ошибок в С++

У меня есть репозиторий в GitHub, где представлены представленные методы.

В принципе, что A.A делает, реализует класс

template<class T>
class Expected { /* Implementation in the GitHub link */ };

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

int foo(); 
// .... 
Expected<int> ret = foo();
if (ret.valid()) {
    // do the work
}
else {
    // either use the info of the exception 
    // or throw the exception (eg in an exception "friendly" codebase)
}

При построении этой структуры для обработки ошибок A.A проводит нас через методы и проекты, которые производят успешную или плохую обработку ошибок и что работает, а что нет. Он также дает свои определения "ошибки" и "обработки ошибок"

Ответ 9

Вот потрясающий пост в блоге, в котором объясняется, как должна выполняться обработка ошибок. http://damienkatz.net/2006/04/error_code_vs_e.html

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

Как решить, как регистрировать ошибку или показывать ее как сообщение об ошибке пользователю? Вероятно, вы никогда не должны показывать ошибку пользователю, если вы так думаете. Скорее, покажите им хорошо сформированное сообщение, объясняющее ситуацию, не давая слишком много технической информации. Затем запишите техническую информацию, особенно если это ошибка при обработке ввода. Если ваш код не знает, как обрабатывать ошибочный ввод, тогда это ДОЛЖНО быть исправлено.

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

В случае исключений, где вы обычно их поймаете? В коде низкого уровня или более высокого уровня? См. Вопрос 1.

Аналогичный вопрос: в какой момент вы должны прекратить распространять ошибку и справляться с ней? См. Вопрос 1.

Если вы будете стремиться к единой стратегии обработки ошибок через все уровни кода или попытаться разработать систему, которая может адаптироваться к различным стратегиям обработки ошибок (чтобы иметь возможность справляться с ошибками из сторонних библиотек), Отбрасывание исключений - это дорогостоящая операция на большинстве тяжелых языков, поэтому используйте их там, где весь процесс программы нарушен для этой операции. С другой стороны, если вы можете предсказать все результаты функции, поместите любые данные через ссылочную переменную, переданную ей как параметр, и верните код ошибки (0 при успехе, 1+ при ошибках).

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

Ответ 10

Мои два цента.

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

Как решить, как регистрировать ошибку или показывать ее как сообщение об ошибке пользователю? Две ортогональные проблемы, которые не являются взаимоисключающими. Регистрация ошибки в конечном итоге для вас, разработчика. Если вы заинтересованы в этом, запишите его. Покажите его пользователю, если он действителен для пользователя ( "Ошибка: нет сетевого подключения!" ).

Записывает ли что-то, что должно быть сделано только в коде приложения? Или это нормально делать некоторые записи из библиотечного кода. Я не вижу причин, по которым библиотеки не могут регистрироваться.

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

Вы, конечно же, не должны ставить блок try/catch вокруг каждой вызывающей функции, которую вы вызываете.

Аналогичный вопрос: в какой момент вы должны прекратить распространять ошибку и справляться с ней? Если вы будете стремиться к единой стратегии обработки ошибок через все уровни кода или попытаться разработать систему, которая может адаптироваться к различным стратегиям обработки ошибок (чтобы иметь возможность справляться с ошибками из сторонних библиотек). В первом случае вы можете справиться с этим. Этот момент может не существовать, и ваше приложение может потерпеть крах. Затем вы получите хороший аварийный дамп и сможете обновить обработку ошибок.

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

Ответ 11

Как решить, следует ли обрабатывать ошибку локально или распространяться на код более высокого уровня?

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

Как решить, как регистрировать ошибку или показывать ее как сообщение об ошибке пользователю?

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

Записывает ли что-то, что должно быть сделано только в коде приложения? Или это нормально делать некоторые записи из кода библиотеки.

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

В случае исключений, где вы обычно их поймаете? В коде низкого уровня или более высокого уровня?

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

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

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

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

Ответ 12

Первый вопрос, вероятно, что вы можете сделать с ошибкой?

Можете ли вы это исправить (в каком случае вам нужно сообщить пользователю) или пользователь может это исправить?

Если никто не может это исправить, и вы собираетесь выйти, есть ли какое-либо значение в том, что это сообщило вам (через аварийный дамп или код ошибки)?

Ответ 13

Я меняю философию дизайна и кодирования так, чтобы:

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

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

В настоящее время я испытываю проблемы, которые теряются в кодах возврата; или создаются новые коды возврата.

Ответ 14

В книге "Руководства по дизайну рамок: условности, идиомы и шаблоны для многоразовых библиотек .NET" книга Кшиштофа Квалины и Брэда Абрамса содержит несколько хороших предложений по этому вопросу. См. Главу 7 "Исключения". Например, это способствует исключению исключений для возврата кодов ошибок.

-Krip