При программировании по контракту функция или метод сначала проверяют, выполняются ли ее предварительные условия, прежде чем приступить к выполнению своих обязанностей, верно? Два самых известных способа выполнить эти проверки - assert
постепенно exception
.
- assert не работает только в режиме отладки. Чтобы убедиться, что крайне важно (модульное) протестировать все отдельные предварительные условия контракта, чтобы увидеть, действительно ли они не срабатывают.
- исключение не работает в режиме отладки и выпуска. Это имеет то преимущество, что протестированное поведение отладки идентично поведению при выпуске, но влечет за собой снижение производительности во время выполнения.
Как вы думаете, какой из них предпочтительнее?
См. Соответствующий вопрос здесь
exception
assert
design-by-contract
Андреас BuyKX
источник
источник
Ответы:
Отключение assert в сборках выпуска - это как сказать: «У меня никогда не будет никаких проблем в сборке выпуска», что часто бывает не так. Поэтому assert не следует отключать в сборке релиза. Но вы же не хотите, чтобы сборка релиза падала при возникновении ошибок, не так ли?
Так что используйте исключения и используйте их правильно. Используйте хорошую, надежную иерархию исключений и убедитесь, что вы перехватываете и можете перехватить выброс исключения в отладчике, чтобы отловить его, а в режиме выпуска вы можете компенсировать ошибку, а не просто сбой. Это более безопасный путь.
источник
Эмпирическое правило заключается в том, что вы должны использовать утверждения, когда пытаетесь отловить свои собственные ошибки, и исключения, когда пытаетесь отловить чужие ошибки. Другими словами, вы должны использовать исключения для проверки предварительных условий для общедоступных функций API и всякий раз, когда вы получаете какие-либо данные, которые являются внешними по отношению к вашей системе. Вы должны использовать утверждения для функций или данных, которые являются внутренними для вашей системы.
источник
Я придерживаюсь следующего принципа: если ситуацию можно реально избежать с помощью кодирования, используйте утверждение. В противном случае используйте исключение.
Утверждения предназначены для обеспечения соблюдения Контракта. Контракт должен быть справедливым, чтобы клиент мог гарантировать его выполнение. Например, вы можете указать в контракте, что URL-адрес должен быть действительным, поскольку правила о том, что является действительным, а что нет, известны и согласованы.
Исключения составляют ситуации, которые находятся вне контроля как клиента, так и сервера. Исключение означает, что что-то пошло не так, и ничего нельзя было сделать, чтобы этого избежать. Например, подключение к сети находится вне контроля приложений, поэтому ничего нельзя сделать, чтобы избежать сетевой ошибки.
Я хотел бы добавить, что различие между утверждениями и исключениями - не лучший способ думать об этом. На самом деле вы хотите думать о контракте и о том, как его можно обеспечить. В приведенном выше примере с URL-адресом лучше всего иметь класс, который инкапсулирует URL-адрес и имеет либо значение Null, либо действительный URL-адрес. Это преобразование строки в URL-адрес, который обеспечивает выполнение контракта, и если он недействителен, выдается исключение. Метод с параметром URL-адреса намного понятнее, чем метод с параметром String и утверждение, указывающее URL-адрес.
источник
Утверждения предназначены для выявления того, что разработчик сделал неправильно (не только вас, но и другого разработчика в вашей команде). Если это разумно, что ошибка пользователя могла создать это условие, тогда это должно быть исключением.
Точно так же подумайте о последствиях. Утверждение обычно закрывает приложение. Если есть какое-либо реалистичное ожидание того, что состояние может быть восстановлено, вам, вероятно, следует использовать исключение.
С другой стороны, если эта проблема может только быть из - за ошибки программиста затем использовать Assert, потому что вы хотите как можно скорее узнать об этом. Исключение может быть обнаружено и обработано, и вы никогда не узнаете об этом. И да, вы должны отключить утверждения в коде выпуска, потому что там вы хотите, чтобы приложение восстановилось, если есть малейшая вероятность, что это может. Даже если состояние вашей программы сильно нарушено, пользователь может сохранить свою работу.
источник
Не совсем верно, что «assert терпит неудачу только в режиме отладки».
В « Объектно-ориентированном программном обеспечении», 2-е издание Бертрана Мейера, автор оставляет дверь открытой для проверки предварительных условий в режиме выпуска. В этом случае, когда утверждение не выполняется, возникает ... исключение нарушения утверждения! В этом случае выхода из ситуации нет: все же можно сделать что-то полезное, а именно автоматическое создание отчета об ошибке и, в некоторых случаях, перезапуск приложения.
Причина этого в том, что предварительные условия обычно дешевле тестировать, чем инварианты и постусловия, и что в некоторых случаях правильность и «безопасность» при сборке релиза важнее скорости. т.е. для многих приложений скорость - это не проблема, а надежность (способность программы вести себя безопасным образом, когда ее поведение некорректно, т.е. когда контракт разрывается).
Следует ли всегда оставлять проверку предварительных условий включенной? Это зависит. Тебе решать. Универсального ответа нет. Если вы создаете программное обеспечение для банка, может быть лучше прервать выполнение с помощью тревожного сообщения, чем перевести 1 000 000 долларов вместо 1 000 долларов. Но что, если вы программируете игру? Может быть, вам нужна максимальная скорость, и если кто-то наберет 1000 очков вместо 10 из-за ошибки, которую не удалось уловить в предварительных условиях (потому что они не включены), то не повезло.
В обоих случаях в идеале вы должны обнаружить эту ошибку во время тестирования, и вам следует проводить значительную часть тестирования с включенными утверждениями. Здесь обсуждается наилучшая политика для тех редких случаев, когда предварительные условия не работают в производственном коде в сценарии, который не был обнаружен ранее из-за неполного тестирования.
Подводя итог, можно иметь утверждения и автоматически получать исключения , если оставить их включенными - по крайней мере, в Eiffel. Думаю, чтобы сделать то же самое на C ++, вам нужно набрать его самостоятельно.
См. Также: Когда утверждения должны оставаться в производственном коде?
источник
Был большой поток, касающийся включения / отключения утверждений в сборках релизов на comp.lang.c ++. Moderated, и если у вас есть несколько недель, вы можете увидеть, насколько разные мнения по этому поводу. :)
В отличие от coppro , я считаю, что если вы не уверены, что утверждение можно отключить в сборке выпуска, то оно не должно было быть утверждением. Утверждения предназначены для защиты от нарушения инвариантов программы. В таком случае для клиента вашего кода будет один из двух возможных исходов:
Однако для пользователя нет никакой разницы, возможно, что утверждения добавляют ненужные затраты на производительность в коде, который присутствует в подавляющем большинстве запусков, где код не дает сбоев.
На самом деле ответ на вопрос гораздо больше зависит от того, кто будет клиентами API. Если вы пишете библиотеку, предоставляющую API, вам нужен какой-то механизм, чтобы уведомить ваших клиентов о том, что они использовали API неправильно. Если вы не предоставите две версии библиотеки (одну с утверждениями, а другую без), то утверждение assert вряд ли будет подходящим выбором.
Лично я, однако, не уверен, что буду делать исключения и для этого случая. Исключения лучше подходят для тех случаев, когда может иметь место подходящая форма восстановления. Например, возможно, вы пытаетесь выделить память. Когда вы поймаете исключение 'std :: bad_alloc', возможно, удастся освободить память и повторить попытку.
источник
Я изложил свой взгляд на состояние вопроса здесь: как вы проверяете внутреннее состояние объекта? , В общем, отстаивайте свои претензии и бросайте за нарушение другими. Чтобы отключить утверждения в сборках выпуска, вы можете:
Конечно, в сборках выпуска неудачные утверждения и неперехваченные исключения следует обрабатывать иначе, чем в сборках отладки (где можно просто вызвать std :: abort). Напишите где-нибудь журнал ошибки (возможно, в файл), сообщите клиенту, что произошла внутренняя ошибка. Заказчик сможет выслать вам лог-файл.
источник
вы спрашиваете о разнице между ошибками времени разработки и времени выполнения.
asserts - это уведомления типа «эй, программист, это не работает», они напоминают вам об ошибках, которые вы бы не заметили, когда они произошли.
Исключениями являются уведомления «Эй, пользователь, что-то пошло не так» (очевидно, вы можете написать код, чтобы их поймать, чтобы пользователь никогда не узнал об этом), но они предназначены для того, чтобы происходить во время выполнения, когда пользователь Joe использует приложение.
Итак, если вы думаете, что сможете избавиться от всех своих ошибок, используйте только исключения. Если вы думаете, что не можете ... используйте исключения. Вы по-прежнему можете использовать утверждения отладки, чтобы, конечно же, уменьшить количество исключений.
Не забывайте, что многие из предварительных условий будут данными, предоставленными пользователем, поэтому вам понадобится хороший способ сообщить пользователю, что его данные не годятся. Для этого вам часто нужно возвращать данные об ошибках вниз по стеку вызовов в биты, с которыми он взаимодействует. Тогда утверждения не будут полезны - вдвойне, если ваше приложение n-уровневое.
Наконец, я бы не стал использовать ни то, ни другое - коды ошибок намного лучше подходят для ошибок, которые, как вы думаете, будут происходить регулярно. :)
источник
Я предпочитаю второй. Хотя ваши тесты, возможно, прошли нормально, Мерфи говорит, что что-то неожиданное пойдет не так. Таким образом, вместо того, чтобы получить исключение при фактическом ошибочном вызове метода, вы в конечном итоге отслеживаете исключение NullPointerException (или эквивалент) на 10 кадров стека глубже.
источник
Предыдущие ответы верны: используйте исключения для публичных функций API. Единственный раз, когда вы можете захотеть нарушить это правило, - это когда проверка требует больших вычислительных ресурсов. В этом случае вы можете использовать assert.
Если вы считаете, что нарушение этого предусловия вероятно, сохраните его как исключение или выполните рефакторинг предусловия.
источник
Вы должны использовать оба. Утверждения предназначены для вашего удобства как разработчика. Исключения ловят то, что вы пропустили или не ожидали во время выполнения.
Я полюбил функции отчетов об ошибках в glib вместо старых простых утверждений. Они ведут себя как операторы assert, но вместо остановки программы они просто возвращают значение и позволяют программе продолжаться. Он работает на удивление хорошо, и в качестве бонуса вы можете увидеть, что происходит с остальной частью вашей программы, когда функция не возвращает «то, что она должна». Если он выйдет из строя, вы знаете, что ваша проверка ошибок неэффективна где-то еще в будущем.
В моем последнем проекте я использовал этот стиль функций для реализации проверки предварительных условий, и если одна из них не удалась, я бы распечатал трассировку стека в файл журнала, но продолжал работать. Сэкономил мне массу времени на отладку, когда другие люди сталкивались с проблемой при запуске моей отладочной сборки.
Если бы мне понадобилась проверка аргументов во время выполнения, я бы сделал следующее:
источник
Я попытался синтезировать здесь несколько других ответов со своими собственными взглядами.
Используйте утверждения для случаев, когда вы хотите отключить его в производственной среде, ошибочно оставляя их внутри. Единственная реальная причина для отключения в производственной среде, но не в разработке, - это ускорить работу программы. В большинстве случаев это ускорение не будет значительным, но иногда код критичен по времени или тест требует больших вычислительных ресурсов. Если код критически важен, то исключения могут быть лучше, несмотря на замедление.
Если есть реальная вероятность восстановления, используйте исключение, поскольку утверждения не предназначены для восстановления. Например, код редко предназначен для восстановления после ошибок программирования, но он предназначен для восстановления после таких факторов, как сбои сети или заблокированные файлы. Ошибки не следует рассматривать как исключения просто потому, что они находятся вне контроля программиста. Скорее, предсказуемость этих ошибок по сравнению с ошибками кодирования делает их более удобными для исправления.
Аргумент, что легче отлаживать утверждения: трассировку стека от правильно названного исключения так же легко читать, как и утверждения. Хороший код должен улавливать только определенные типы исключений, поэтому исключения не должны оставаться незамеченными из-за того, что они были пойманы. Однако я думаю, что Java иногда заставляет вас ловить все исключения.
источник
Для меня практическое правило заключается в том, что выражения assert используются для поиска внутренних ошибок и исключений для внешних ошибок. Отсюда вы можете извлечь большую пользу из следующего обсуждения Грега .
PS: вы можете проверить аналогичный вопрос: Исключение против утверждения .
источник
См. Также этот вопрос :
источник