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

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

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

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

Debug.Assert(val != null);

или

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

20 ответов


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

  1. утверждать, обильно. У вас никогда не может быть слишком много утверждений.
  2. утверждения не заменяют исключения. Исключения охватывают то, что требует ваш код; утверждения охватывают то, что он предполагает.
  3. хорошо написанное утверждение может рассказать вам не только о том, что произошло и где (как исключение), но и почему.
  4. An сообщение об исключении часто может быть зашифрованным, требуя от вас работать в обратном направлении через код для воссоздания контекста, вызвавшего ошибку. Утверждение может сохранить состояние программы в момент возникновения ошибки.
  5. утверждения удваиваются как документация, сообщая другим разработчикам, от каких подразумеваемых предположений зависит ваш код.
  6. диалоговое окно, которое появляется при сбое утверждения, позволяет подключить отладчик к процессу, поэтому вы можете ковыряться в стеке, как если бы вы положили точка останова.

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


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

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


с Код

8 Защитное Программирование

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

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

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

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

(...)

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


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


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

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


на вашем месте я бы сделал:

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

или избежать повторной проверки состояния

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

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

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

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

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

в целом,

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

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

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


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

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


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

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

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

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


Короче

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

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

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

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

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

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

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

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

дизайн по контракту (DBC) проверки доступны включают в себя:

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

по словам Стандартные Компании Idesign вы должны

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

using System.Diagnostics;

object GetObject()
{...}

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

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


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

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


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

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 С null, поэтому он возвращает исключение.

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


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

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

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

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

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

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

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

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

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

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

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


цитата взята из прагматичный программист: от подмастерья до мастера

Оставить Утверждения Включены

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

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

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

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

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

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


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

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


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


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

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

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

Trace.Assert имеет идеальный способ достичь этого. Он не будет удален в производстве и может быть настроен с различными слушателями с помощью приложения.конфиг. Поэтому для разработки обработчик по умолчанию прекрасен, а для производства вы можете создать простой 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>

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


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

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