Вопрос

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

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

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

Debug.Assert(val != null);

или

if ( val == null )
    throw new exception();
Это было полезно?

Решение

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

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

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

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

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

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

От Код завершен

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

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

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

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

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

(…)

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

К ВАУ...Я обнаружил, что мои публичные методы, как правило, используют if () { throw; } шаблон, чтобы убедиться, что метод вызывается правильно.Мои частные методы, как правило, используют 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();
}

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

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

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

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

В итоге,

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

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

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

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

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

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

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

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

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

Суммируя

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

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

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

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

...Более детально

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

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

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

Доступные проверки «Проектирование по контракту» (DBC) включают:

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

Согласно Стандартный дизайн, вам следует

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

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != 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 с нулем, поэтому он возвращает исключение.

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

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

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

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

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

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

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

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

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

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

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

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

Цитата взята из Прагматичный программист:От подмастерья к мастеру

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

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

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

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

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

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

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

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

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

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

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

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

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

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>

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

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

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

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