Я работаю профессиональным инженером-программистом около года, закончив со степенью бакалавра. Некоторое время я знал об утверждениях в C ++ и C, но понятия не имел, что они существовали в C # и .NET вообще до недавнего времени.
Наш производственный код не содержит никаких утверждений, и мой вопрос заключается в следующем ...
Должен ли я начать использовать Asserts в нашем производственном коде? И если да, то когда его использование является наиболее подходящим? Будет ли больше смысла делать
Debug.Assert(val != null);
или
if ( val == null )
throw new exception();
language-agnostic
exception
testing
assertions
defensive-programming
Николас Манкузо
источник
источник
Ответы:
В отладке приложений Microsoft .NET 2.0 у Джона Роббинса большой раздел, посвященный утверждениям. Его основные моменты:
PS: Если вам понравился Code Complete, я рекомендую дополнить его этой книгой. Я купил его, чтобы узнать, как использовать WinDBG и файлы дампа, но первая половина содержит советы, которые помогут избежать ошибок.
источник
Debug.Assert
противTrace.Assert
. Последний выполняется в сборке Release, а также в сборке Debug.Поместите
Debug.Assert()
везде в коде, где вы хотите, иметь проверки работоспособности для обеспечения инвариантов. Когда вы компилируете сборку Release (т.е. безDEBUG
константы компилятора), вызовыDebug.Assert()
будут удалены, чтобы они не влияли на производительность.Вы все равно должны бросить исключения перед вызовом
Debug.Assert()
. Assert просто гарантирует, что все идет так, как вы ожидаете, пока вы все еще развиваетесь.источник
Из полного кода
источник
FWIW ... Я считаю, что мои публичные методы имеют тенденцию использовать
if () { throw; }
шаблон, чтобы гарантировать, что метод вызывается правильно. Мои частные методы имеют тенденцию использоватьDebug.Assert()
.Идея состоит в том, что с моими личными методами я контролирую себя, поэтому, если я начну вызывать один из моих собственных частных методов с неверными параметрами, то я где-то нарушу свое собственное предположение - я никогда не должен был получить в это состояние. В производстве эти частные утверждения в идеале должны быть ненужной работой, поскольку я должен сохранять свое внутреннее состояние действительным и последовательным. Сравните с параметрами, переданными публичным методам, которые могут быть вызваны кем-либо во время выполнения: мне все еще нужно применять ограничения параметров, создавая исключения.
Кроме того, мои частные методы могут по-прежнему генерировать исключения, если что-то не работает во время выполнения (ошибка сети, ошибка доступа к данным, неверные данные, полученные из сторонней службы и т. Д.). Мои утверждения только для того, чтобы убедиться, что я не нарушил свои внутренние предположения о состоянии объекта.
источник
Используйте утверждения, чтобы проверить предположения разработчика и исключения, чтобы проверить предположения среды.
источник
На вашем месте я бы сделал:
Или чтобы избежать повторной проверки состояния
источник
Если вы хотите использовать Asserts в своем производственном коде (т. Е. Выпусках сборки), вы можете использовать Trace.Assert вместо Debug.Assert.
Это, конечно, увеличивает накладные расходы на ваш производственный исполняемый файл.
Также, если ваше приложение работает в режиме пользовательского интерфейса, диалоговое окно подтверждения будет отображаться по умолчанию, что может немного смущать ваших пользователей.
Вы можете переопределить это поведение, удалив DefaultTraceListener: посмотрите документацию для Trace.Listeners в MSDN.
В итоге,
Используйте Debug.Assert свободно, чтобы помочь выявить ошибки в сборках Debug.
Если вы используете Trace.Assert в режиме пользовательского интерфейса, вы, вероятно, захотите удалить DefaultTraceListener, чтобы избежать смущения пользователей.
Если тестируемое вами условие является тем, что ваше приложение не может обработать, вам, вероятно, лучше создать исключение, чтобы выполнение не продолжалось. Имейте в виду, что пользователь может игнорировать утверждение.
источник
Активы используются для обнаружения ошибки программиста (вашей), а не ошибки пользователя. Их следует использовать только в том случае, если нет никаких шансов, что пользователь может вызвать утверждение. Например, если вы пишете API, утверждения не должны использоваться для проверки того, что аргумент не является нулевым в любом методе, который может вызвать пользователь API. Но его можно использовать в закрытом методе, не являющемся частью вашего API, чтобы утверждать, что ВАШ код никогда не передает нулевой аргумент, если это не предполагается.
Я обычно предпочитаю исключения, чем утверждения, когда я не уверен.
источник
Коротко
Asserts
используются для охранников и для проверки проектирования по условиям контракта, а именно:Asserts
должен быть только для отладочных и непроизводственных сборок. Утверждения обычно игнорируются компилятором в сборках Release.Asserts
может проверить на наличие ошибок / неожиданных условий, которые находятся под контролем вашей системыAsserts
НЕ являются механизмом для проверки первой строки пользовательского ввода или бизнес-правилAsserts
следует не использоваться для обнаружения неожиданных экологических условий (которые находятся вне контроля кода) , например , из памяти, сетевого сбоя, сбоя базы данных и т.д. Хотя редко, эти условия следует ожидать (и ваш код приложения не может решить такие вопросы , как аппаратный сбой или истощение ресурсов). Как правило, генерируются исключения - ваше приложение может либо предпринять корректирующие действия (например, повторить попытку базы данных или выполнить сетевую операцию, попытаться освободить кэшированную память), либо изящно прервать работу, если исключение не может быть обработано.Asserts
- ваш код работает на неожиданной территории. Следы стека и аварийные дампы могут быть использованы, чтобы определить, что пошло не так.Утверждения имеют огромное преимущество:
Debug
сборках.... Более детально
Debug.Assert
выражает условие, которое было принято о состоянии оставшейся частью блока кода в элементе управления программы. Это может включать в себя состояние предоставленных параметров, состояние членов экземпляра класса или то, что возврат от вызова метода находится в его контрактном / разработанном диапазоне. Как правило, утверждения должны приводить к аварийному завершению потока / процесса / программы со всей необходимой информацией (отслеживание стека, аварийный дамп и т. Д.), Поскольку они указывают на наличие ошибки или неучтенного условия, для которого не было разработано (то есть не пытаться поймать или обрабатывать ошибки подтверждения), с одним возможным исключением того, что само утверждение может нанести больше ущерба, чем ошибка (например, авиадиспетчеры не хотят YSOD, когда самолет идет на подводной лодке, хотя это спорный вопрос, следует ли развертывать отладочную сборку на производство ...)Когда следует использовать
Asserts?
- В любой точке системы, или API библиотеки, или службы, где входные данные для функции или состояния класса считаются действительными (например, когда проверка уже была произведена при вводе данных пользователем на уровне представления системы классы бизнес-уровня и уровня данных обычно предполагают, что проверки нуля, проверки диапазона, проверки длины строки и т. д. на входе уже выполнены). - К общимAssert
проверкам относятся случаи, когда недопустимое допущение приведет к разыменованию нулевого объекта, делителю нуля, числовому переполнению или арифметическому переполнению даты и общепринятым / не предназначенным для поведения (например, если 32-разрядное целое число использовалось для моделирования возраста человека). было бы разумноAssert
возраст фактически составлял от 0 до 125 или около того - значения от -100 до 10 ^ 10 не были рассчитаны).Контракты .Net Code
В стеке .Net контракты Code могут использоваться в дополнение или в качестве альтернативы использованию
Debug.Assert
. Контракты на кодирование могут дополнительно формализовать проверку состояния и могут помочь в выявлении нарушений допущений во время компиляции (или вскоре после этого, если они запускаются в качестве фоновой проверки в IDE).Доступны проверки по контракту (DBC):
Contract.Requires
- Договорные условияContract.Ensures
- Контрактные условияInvariant
- Выражает предположение о состоянии объекта на всех этапах его жизни.Contract.Assumes
- Устанавливает статическую проверку при вызове методов, не оформленных в соответствии с Контрактом.источник
В основном никогда в моей книге. В подавляющем большинстве случаев, если вы хотите проверить, все ли в порядке, бросьте, если это не так.
Что мне не нравится, так это то, что отладочная сборка функционально отличается от сборки выпуска. Если отладочное утверждение не удается, но функциональность работает в выпуске, то как это имеет смысл? Еще лучше, когда ассистент давно покинул компанию, и никто не знает эту часть кода. Затем вам придется потратить некоторое время на изучение проблемы, чтобы понять, действительно ли это проблема или нет. Если это проблема, то почему человек не бросает в первую очередь?
Для меня это говорит о том, что, используя Debug.Asserts, вы переносите проблему на кого-то другого, решаете проблему самостоятельно. Если что-то должно быть, а это не так, бросьте.
Я предполагаю, что, возможно, существуют сценарии, критичные к производительности, когда вы хотите оптимизировать свои утверждения, и они там полезны, однако я еще не сталкивался с таким сценарием.
источник
System.Diagnostics.Trace.Assert()
выполняется как в сборке Release, так и в сборке Debug.Согласно стандарту IDesign , вы должны
В качестве отказа от ответственности я должен упомянуть, что я не нашел практичным реализовать этот IRL. Но это их стандарт.
источник
Используйте утверждения только в тех случаях, когда вы хотите убрать проверку для сборок релиза. Помните, что ваши утверждения не сработают, если вы не скомпилируете в режиме отладки.
Учитывая ваш пример проверки нуля, если это только для внутреннего API, я мог бы использовать утверждение. Если бы он был в публичном API, я бы определенно использовал явные check и throw.
источник
System.Diagnostics.Trace.Assert()
утверждение для выпуска (производственной) сборки.null
когда: «видимый извне метод разыменовывает один из своих ссылочных аргументов, не проверяя, является ли этот аргумент нулевым ». В такой ситуации метод или свойство должны быть выброшеныArgumentNullException
.Все утверждения должны быть кодом, который можно оптимизировать для:
Потому что он проверяет то, что вы уже предположили, правда. Например:
Выше есть три разных подхода к нулевым параметрам. Первый принимает это как допустимое (он просто ничего не делает). Второй генерирует исключение для вызывающего кода для обработки (или нет, что приводит к сообщению об ошибке). Третий предполагает, что это не может произойти, и утверждает, что это так.
В первом случае проблем нет.
Во втором случае есть проблема с вызывающим кодом - он не должен был вызываться
GetFirstAndConsume
с нулем, поэтому он возвращает исключение.В третьем случае есть проблема с этим кодом, потому что он должен был уже быть проверен,
en != null
прежде чем он когда-либо был вызван, так что это не правда, это ошибка. Или, другими словами, это должен быть код, который теоретически можно оптимизироватьDebug.Assert(true)
, sicneen != null
всегда должен бытьtrue
!источник
en == null
в производстве? Возможно, вы говорите, чтоen == null
это никогда не произойдет в производстве (так как программа была тщательно отлажена)? Если это так, то,Debug.Assert(en != null)
по крайней мере, служит альтернативой комментарию. Конечно, если будущие изменения будут сделаны, это также будет иметь значение для обнаружения возможной регрессии.Debug.Assert()
удаляются в сборке Release. Так что, если вы не правы, в третьем случае вы не узнаете об этом в производственной среде (при условии использования выпускаемой сборки в производственной среде). Однако поведение первого и второго случаев идентично в сборках Debug и Release.Я думал, что добавлю еще четыре случая, когда Debug.Assert может быть правильным выбором.
1) Что-то, что я не видел, упоминалось здесь, это дополнительное концептуальное покрытие, которое Asserts может обеспечить во время автоматизированного тестирования . В качестве простого примера:
Когда автор вызова, который считает, что они расширили область действия кода для обработки дополнительных сценариев, изменяет некоторых вызывающих абонентов более высокого уровня, в идеале (!) Они будут писать модульные тесты, чтобы охватить это новое условие. Тогда может оказаться, что полностью интегрированный код работает нормально.
Однако на самом деле был выявлен тонкий дефект, но он не обнаружен в результатах испытаний. Вызываемый стал недетерминированным в этом случае, и только происходит чтобы обеспечить ожидаемый результат. Или, возможно, это привело к ошибке округления, которая была незамеченной. Или вызвал ошибку, которая была одинаково смещена в другом месте. Или предоставляется не только запрошенный доступ, но и дополнительные привилегии, которые не должны предоставляться. И т.п.
На этом этапе операторы Debug.Assert (), содержащиеся в вызываемом объекте, вместе с новым регистром (или граничным регистром), инициируемым модульными тестами, могут предоставить неоценимое уведомление во время теста о том, что исходные предположения автора были признаны недействительными, и код не должен быть выпущенным без дополнительного рассмотрения. Активы с юнит-тестами являются идеальными партнерами.
2) Кроме того, некоторые тесты просты в написании, но дорогостоящи и не нужны, учитывая первоначальные предположения . Например:
Если доступ к объекту возможен только из определенной защищенной точки входа, следует ли выполнить дополнительный запрос к базе данных сетевых прав из каждого метода объекта, чтобы убедиться, что у вызывающего пользователя есть разрешения? Конечно нет. Возможно, идеальное решение включает в себя кэширование или какое-то другое расширение функций, но дизайн не требует этого. Debug.Assert () немедленно покажет, когда объект был присоединен к небезопасной точке входа.
3) Далее, в некоторых случаях ваш продукт может не иметь полезного диагностического взаимодействия для всех или части его операций при развертывании в режиме выпуска . Например:
Предположим, это встроенное устройство реального времени. Бросать исключения и перезапускать при обнаружении некорректного пакета нецелесообразно. Вместо этого устройство может извлечь выгоду из работы с максимальным усилием, даже до точки рендеринга шума на его выходе. Он также может не иметь человеческого интерфейса, регистрирующего устройства или даже быть физически недоступным для человека вообще при развертывании в режиме выпуска, и понимание ошибок лучше всего обеспечить путем оценки того же результата. В этом случае либеральные утверждения и тщательное предварительное тестирование более ценны, чем исключения.
4) Наконец, некоторые тесты являются ненужными только потому, что вызываемый абонент воспринимается как чрезвычайно надежный . В большинстве случаев, чем больше повторного использования кода, тем больше усилий было приложено, чтобы сделать его надежным. Следовательно, для исключения характерны непредвиденные параметры от вызывающих абонентов, а для ожидаемых результатов от вызывающих - обычное явление. Например:
Если основная
String.Find
операция заявляет, что она вернет,-1
когда критерии поиска не найдены, вы можете безопасно выполнить одну операцию, а не три. Однако, если это действительно вернулось-2
, у вас может не быть разумных действий. Было бы бесполезно заменять более простой расчет на тот, который проверяет отдельно-1
значение, и в большинстве сред выпуска нецелесообразно засорять ваш код тестами, гарантирующими, что основные библиотеки работают должным образом. В этом случае утверждения идеальны.источник
Цитата, взятая у прагматичного программиста: от подмастерье до мастера
источник
Вы должны всегда использовать второй подход (выбрасывая исключения).
Кроме того, если вы работаете (и имеете релиз-сборку), лучше создать исключение (и позволить приложению аварийно завершить работу в худшем случае), чем работать с недопустимыми значениями и, возможно, уничтожить данные вашего клиента (что может стоить тысячи долларов).
источник
Вы должны использовать Debug.Assert для проверки на логические ошибки в ваших программах. Компилятор может информировать вас только о синтаксических ошибках. Поэтому вы должны определенно использовать операторы Assert для проверки на логические ошибки. Как, например, тестирование программы, которая продает автомобили, которые только синие BMW должны получить скидку 15%. Компилятор может ничего вам не сказать, если ваша программа логически верна в выполнении этого, но оператор assert может.
источник
Я прочитал ответы здесь и подумал, что должен добавить важное различие. Есть два очень разных способа использования утверждений. Один из них - это временное ярлык разработчика для «Это не должно действительно происходить, поэтому, если это действительно так, дайте мне знать, чтобы я мог решить, что делать», вроде условной точки останова, для случаев, когда ваша программа может продолжить работу. Другой - это способ добавить в ваш код предположения о допустимых состояниях программы.
В первом случае утверждения даже не должны быть в конечном коде. Вы должны использовать
Debug.Assert
во время разработки, и вы можете удалить их, если / когда больше не нужны. Если вы хотите оставить их или забыть удалить их, не проблема, так как они не будут иметь никакого значения в компиляциях Release.Но во втором случае утверждения являются частью кода. Они, ну, утверждают, что ваши предположения верны, и также документируют их. В этом случае вы действительно хотите оставить их в коде. Если программа находится в недопустимом состоянии, ее нельзя продолжать. Если бы вы не могли позволить себе хит производительности, вы бы не использовали C #. С одной стороны, было бы полезно иметь возможность подключить отладчик, если это произойдет. С другой стороны, вы не хотите, чтобы трассировка стека появлялась у ваших пользователей, и, возможно, более важно, что вы не хотите, чтобы они могли ее игнорировать. Кроме того, если он находится в сервисе, он всегда будет игнорироваться. Следовательно, в производстве правильным поведением было бы создание исключения и использование обычной обработки исключений вашей программы, которая могла бы показать пользователю приятное сообщение и записать подробности.
Trace.Assert
имеет идеальный способ достичь этого. Он не будет удален в производственном процессе и может быть настроен для разных слушателей с помощью app.config. Поэтому для разработки подходит стандартный обработчик, а для производства вы можете создать простой TraceListener, как показано ниже, который генерирует исключение и активирует его в файле конфигурации производства.И в файле конфигурации производства:
источник
Я не знаю, как это происходит в C # и .NET, но в C assert () будет работать только если скомпилировано с -DDEBUG - конечный пользователь никогда не увидит assert (), если он скомпилирован без. Это только для разработчиков. Я использую его очень часто, иногда легче отслеживать ошибки.
источник
Я бы не использовал их в производственном коде. Бросай исключения, лови и логи.
Также нужно быть осторожным в asp.net, так как assert может появиться на консоли и заморозить запрос (ы).
источник