Проектирование по контракту с использованием утверждений или исключений?[закрыто]

StackOverflow https://stackoverflow.com/questions/117171

Вопрос

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

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

Как вы думаете, какой из них предпочтительнее?

См. связанный вопрос здесь

Это было полезно?

Решение

Отключение утверждения в сборках выпуска — это все равно, что сказать: «У меня никогда не будет никаких проблем в сборке выпуска», что часто не соответствует действительности.Поэтому утверждение не должно быть отключено в сборке выпуска.Но вы же не хотите, чтобы сборка релиза завершалась сбоем при возникновении ошибок, не так ли?

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

Другие советы

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

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

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

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

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

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

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

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

Не совсем верно, что «утверждение не работает только в режиме отладки».

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

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

Следует ли всегда оставлять проверку предварительных условий включенной?Это зависит.Тебе решать.Универсального ответа не существует.Если вы делаете программное обеспечение для банка, возможно, лучше прервать выполнение тревожным сообщением, чем перевести 1 000 000 долларов вместо 1 000 долларов.Но что, если вы программируете игру?Возможно, вам нужна вся возможная скорость, и если кто-то получит 1000 очков вместо 10 из-за ошибки, которую не обнаружили предварительные условия (потому что они не включены), не повезло.

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

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

Смотрите также: Когда утверждения должны оставаться в рабочем коде?

Там было огромное нить относительно включения/отключения утверждений в релизных сборках на comp.lang.c++.moderated, и если у вас есть несколько недель, вы увидите, насколько различаются мнения по этому поводу.:)

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

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

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

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

Однако лично я не уверен, что и в этом случае я бы пошел на исключения.Исключения лучше подходят для случаев, когда возможна соответствующая форма восстановления.Например, возможно, вы пытаетесь выделить память.Когда вы поймаете исключение «std::bad_alloc», возможно, можно будет освободить память и повторить попытку.

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

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

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

вы спрашиваете о разнице между ошибками времени разработки и времени выполнения.

Утверждения — это уведомления типа «эй, программист, это сломано», они призваны напоминать вам об ошибках, которые вы не заметили бы, когда они произошли.

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

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

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

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

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

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

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

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

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

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

#ifdef DEBUG
#define RETURN_IF_FAIL(expr)      do {                      \
 if (!(expr))                                           \
 {                                                      \
     fprintf(stderr,                                        \
        "file %s: line %d (%s): precondition `%s' failed.", \
        __FILE__,                                           \
        __LINE__,                                           \
        __PRETTY_FUNCTION__,                                \
        #expr);                                             \
     ::print_stack_trace(2);                                \
     return;                                                \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                   \
 {                                                              \
    fprintf(stderr,                                             \
        "file %s: line %d (%s): precondition `%s' failed.",     \
        __FILE__,                                               \
        __LINE__,                                               \
        __PRETTY_FUNCTION__,                                    \
        #expr);                                                 \
     ::print_stack_trace(2);                                    \
     return val;                                                \
 };               } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif

Если бы мне нужна была проверка аргументов во время выполнения, я бы сделал это:

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.
                                            // Goes away when debug off.

    if( ptr != NULL )
    {
       ...
    }

    return ptr;
}

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

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

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

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

Смотрите также этот вопрос:

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

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

Обычно я добавляю исключение в IF-пост, чтобы взять на себя роль утверждения на случай, если они отключены

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff

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

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

Чтобы понять, как утверждения вписываются в программу, рассмотрим процедуру в программе C ++, которая собирается определить указатель.Теперь должно быть обычное проверить, является ли указатель нулевым перед тем, как привязки, или он должен утверждать, что указатель не является нулевым, а затем продолжить и оставить его независимо?

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

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

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

ПС:вы можете проверить аналогичный вопрос: Исключение против утверждения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top