Когда следует использовать Debug.Assert()?

Я работаю профессиональным инженером-программистом около года, окончив степень CS. Я некоторое время знал об утверждениях на С++ и C, но понятия не имел, что они существовали на С# и .NET до недавнего времени.

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

Должен ли я начинать использовать Asserts в нашем производственном коде? И если да, то когда его использование наиболее подходит? Было бы более целесообразно делать

Debug.Assert(val != null);

или

if ( val == null )
    throw new exception();

Ответ 1

В Отладка приложений Microsoft.NET 2.0 Джон Роббинс имеет большой раздел по утверждениям. Его основные пункты:

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

PS: Если вам понравился Code Complete, я рекомендую ознакомиться с этой книгой. Я купил его, чтобы узнать об использовании файлов WinDBG и дампов, но в первой половине есть советы, которые помогут избежать ошибок в первую очередь.

Ответ 2

Поместите Debug.Assert() всюду в код, где вы хотите иметь проверки на здравомыслие, чтобы обеспечить инварианты. Когда вы компилируете константу компилятора Release (т.е. No DEBUG), вызовы на Debug.Assert() будут удалены, поэтому они не повлияют на производительность.

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

Ответ 3

Из Код завершен

8 Оборонительное программирование

8.2 Утверждения

Утверждение - это код, который используется во время разработки - обычно это обычная процедура или макрос, который позволяет программе проверять себя по мере ее запуска. Когда утверждение верно, значит, все работает так, как ожидалось. Когда это ложно, это означает, что он обнаружил непредвиденную ошибку в код. Например, если система предполагает, что информация о клиенте файл никогда не будет иметь более 50 000 записей, программа может содержат утверждение о том, что количество записей ограничено или равно до 50 000. Пока количество записей меньше или равно 50 000, утверждение будет молчать. Если он встречается больше, чем 50 000 записей, однако, он будет громко "утверждать", что есть ошибка в программе.

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

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

(...)

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

Ответ 4

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

Ответ 5

FWIW... Я обнаружил, что мои общедоступные методы имеют тенденцию использовать шаблон if () { throw; } для обеспечения правильного вызова метода. Мои частные методы имеют тенденцию использовать Debug.Assert().

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

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

Ответ 6

Если бы я был вами, я бы сделал:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

Или, чтобы избежать повторной проверки условий

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}

Ответ 7

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

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

Ответ 8

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

Это, конечно, добавляет накладные расходы на ваш производственный исполняемый файл.

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

Вы можете переопределить это поведение, удалив DefaultTraceListener: посмотрите документацию для Trace.Listeners в MSDN.

Таким образом,

  • Используйте Debug.Assert для облегчения поиска ошибок в сборках Debug.

  • Если вы используете Trace.Assert в режиме пользовательского интерфейса, вы, вероятно, захотите удалить DefaultTraceListener, чтобы не смущать пользователей.

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

Ответ 9

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

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

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

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

Ответ 10

Согласно IDesign Standard, вы должны

Утвердить каждое предположение. В среднем каждая пятая строка является утверждением.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

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

Ответ 11

Короче

  • Asserts проверьте наличие ошибок/неожиданных условий, которые ARE контролируют вашу систему.
  • Asserts НЕ являются механизмом проверки правильности ввода данных или бизнес-правил.
  • Asserts не используются для обнаружения неожиданных условий окружающей среды (которые находятся вне контроля кода), например. из-за нехватки памяти, сбоя в сети, сбоя базы данных и т.д. Хотя это редко, эти условия следует ожидать (и ваш код приложения не может исправить такие проблемы, как отказ оборудования или исчерпание ресурса).
  • Не пытайтесь поймать или обработать Asserts - ваш код работает на неожиданной территории. Трассировки стека и аварийные дампы могут использоваться для определения того, что пошло не так.

Утверждения имеют огромную пользу:

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

... Подробнее

Debug.Assert выражает условие, которое было принято относительно состояния остальной частью кодового блока в рамках управления программой. Это может включать состояние предоставленных параметров, состояние членов экземпляра класса или возврат возврата от вызова метода в сокращенном/разработанном диапазоне. Как правило, утверждения должны приводить к сбою потока/процесса/программы со всей необходимой информацией (Stack Trace, Crash Dump и т.д.), Поскольку они указывают на наличие ошибки или нерассмотренного состояния, которое не было разработано (т.е. Не пытайтесь поймать или устраняйте ошибки утверждения), за одним исключением, когда само утверждение может нанести больший ущерб, чем ошибка (например, диспетчеры воздушного движения не хотят, чтобы YSOD, когда самолет отправляется на подводную лодку, хотя он спорит, следует ли развертывать отладочную сборку до производство...)

Когда вы должны использовать Asserts?  - В любой момент в системе или API-интерфейс библиотеки или служба, в которой допустимы входные данные для функции или состояния класса (например, когда выполняется проверка на входе пользователя в уровне представления системы, бизнес и данные классы уровня обычно предполагают, что нулевые проверки, проверки диапазона, проверки длины строк и т.д. на входе уже выполнены).  - Общие проверки Assert включают, где недопустимое предположение приведет к разучению нулевого объекта, делителю нуля, численному или арифметическому переполнению дат и вообще внеполосному/не предназначенному для поведения (например, моделирование человеческого возраста как 32-битного целого числа учитывая грубую зернистость большинства систем типов, вероятно, предполагается, что возраст фактически составляет от 0 до 125 или около того).

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

Это увеличивает количество проверок стиля DBC за время выполнения Assert:

  • Contract.Requires - Предварительные условия контракта
  • Contract.Ensures - Контрактные PostConditions
  • Invariant - выражает предположение о состоянии объекта в любой точке его жизни.
  • Contract.Assumes - упрощает статическую проверку, когда делается вызов методов, не предусмотренных контрактом.

Ответ 12

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

Учитывая ваш пример check-for-null, если это в API только для внутреннего использования, я могу использовать утверждение. Если это в публичном API, я бы определенно использовал явную проверку и бросок.

Ответ 13

Все утверждения должны представлять собой код, который можно оптимизировать для:

Debug.Assert(true);

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

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

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

В первом случае проблем нет.

Во втором случае возникает проблема с вызывающим кодом - он не должен был называть GetFirstAndConsume с нулевым значением, поэтому он возвращает исключение.

В третьем случае возникает проблема с этим кодом, поскольку он должен быть уже проверен, что en != null до того, как он когда-либо был вызван, так что это неверно, это ошибка. Или, другими словами, это должен быть код, который теоретически может быть оптимизирован для Debug.Assert(true), sicne en != null всегда должен быть true!

Ответ 14

Я думал, что добавлю еще четыре случая, где Debug.Assert может быть правильным выбором.

1) Что-то, о чем я не упоминал здесь, - это дополнительный концептуальный охват, который могут быть предоставлены во время автоматического тестирования. В качестве простого примера:

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

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

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

2) Кроме того, некоторые тесты просты в написании, но дорогостоящие и ненужные, учитывая исходные предположения. Например:

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

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

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

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

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

Ответ 15

Цитата, взятая из Прагматический программист: от Journeyman to Master

Оставлять утверждённые утверждения

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

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

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

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

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

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

Ответ 16

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

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

Ответ 17

Вы должны использовать Debug.Assert для проверки логических ошибок в ваших программах. Компилятор может информировать вас только о синтаксических ошибках. Поэтому вы должны определенно использовать утверждения Assert для проверки логических ошибок. Как сказать, тестирование программы, которая продает автомобили, что только BMW, которые являются синими, должны получить 15% скидку. Компилятор ничего не мог рассказать вам, если ваша программа логически корректна при выполнении этого, но утверждение assert может.

Ответ 18

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

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

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

Trace.Assert имеет идеальный способ достичь этого. Он не будет удален в процессе производства и может быть настроен с различными слушателями, используя app.config. Поэтому для разработки обработчик по умолчанию прекрасен, и для производства вы можете создать простой TraceListener, как показано ниже, который генерирует исключение и активирует его в файле конфигурации производства.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

И в файле конфигурации производства:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>

Ответ 19

Я не знаю, как это происходит на С# и .NET, но в C будет assert() работать только при компиляции с -DDEBUG - enduser никогда не увидит assert(), если он скомпилирован без. Это только для разработчиков. Я использую его очень часто, иногда легче отслеживать ошибки.

Ответ 20

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

Также нужно быть осторожным в asp.net, так как утверждение может появиться на консоли и заморозить запрос (ы).