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


220

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

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

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

Debug.Assert(val != null);

или

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

2
Дихотомия, которую вы создали, является ключом. Речь идет не об исключениях и утверждениях, а об их и о защитном коде. Когда делать то, что вы хотите понять.
Каспер Леон Нильсен

5
Однажды я прочитал, что кто-то предполагает, что исключение или другой метод сбоя подходит для условий, в которых «Я не могу разумно оправиться от этого», и, кроме того, утверждение подходит для условий, в которых «Это никогда не должно происходить никогда». Но какие реальные обстоятельства удовлетворяют последним условиям, но не удовлетворяют первым? Исходя из опыта работы с Python, когда утверждения остаются в работе, я никогда не понимал подход Java / C #, заключающийся в отключении некоторых ваших проверок в работе. Единственный случай, по которому я могу это увидеть, - дорогая ли проверка.
Марк Эмери


2
Лично я использую исключения для открытых методов и утверждения для частных методов.
Фред

Ответы:


230

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

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

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


3
+1 за краткое и полезное резюме. Очень прямо применимо. Главное, чего мне не хватает, - это когда использовать Trace.Assert или Trace.Assert. Т.е. что-то о том, когда вы делаете / не хотите их в своем рабочем коде.
Джон Кумбс

2
JonCoombs является "Trace.Assert против Trace.Assert" опечатка?
телем

1
@thelem Возможно, Джон имел в виду Debug.Assertпротив Trace.Assert. Последний выполняется в сборке Release, а также в сборке Debug.
DavidRR

Почему я должен предпочесть Debug.Assert выбрасыванию исключения?
Барыш Аккурт

86

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

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


35
Не могли бы вы уточнить, зачем вставлять утверждение, если вы по-прежнему выбрасываете исключение перед его вызовом? Или я неправильно понял ваш ответ?
Роман Старков

2
@romkyns Вы по-прежнему должны включать их, потому что, если вы этого не сделаете, то при сборке проекта в режиме выпуска все проверки / проверки ошибок пропадут.
Оскар Медерос

28
@ Оскар Я думал, что в этом и заключается смысл использования утверждений ... Хорошо, тогда вы ставите исключения перед ними - тогда зачем ставить утверждения после?
Роман Старков

4
@superjos: Я должен не согласиться: Пункт № 2 в ответе МакЛауда гласит, что вам действительно нужно утверждение И исключения, но не в том же месте. Бесполезно бросать NullRefEx в переменную и сразу после этого выполнять Assert (метод assert в этом случае никогда не будет показывать dialogBox, который является целым пунктом утверждения). Что означает Маклеод, так это то, что в некоторых местах вам понадобится исключение, в других достаточно будет Assert.
Дэвид

1
Может быть, не совсем понятно, как истолковывать мою интерпретацию чужого ответа :) В любом случае, я согласен с вами на них: вам нужны оба, и вы не должны ставить исключение перед утверждением. Я не уверен в значении «не в том же месте». Опять же, отказываясь интерпретировать, я просто изложу свои мысли / предпочтения: поставьте одно или несколько утверждений, чтобы проверить предварительные условия перед началом какой-либо операции или проверить постусловия после операции. Кроме утверждений, и в любом случае после них, проверьте, если что-то идет не так и нужно ли генерировать исключения.
Superjos

52

Из полного кода

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

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

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

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

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

(...)

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


7
Итак, что произойдет, если файл с информацией о клиенте, обнаруженный в производстве, содержит более 50 000 записей? Если утверждение скомпилировано из производственного кода, и эта ситуация не обрабатывается иначе, разве это не проблема?
DavidRR

1
@DavidRR Да, действительно. Но как только продукт сообщает о проблеме, и какой-то разработчик (который, возможно, не знает этот код) отладит проблему, утверждение потерпит неудачу, и разработчик сразу узнает, что система используется не по назначению.
Марк

48

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

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

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


3
Это очень четкое описание хорошей практики и дает очень разумный ответ на заданный вопрос.
Каспер Леон Нильсен

42

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


31

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

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

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

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

5
Как это решает проблему? С этим debug.assert становится бессмысленным.
Ужасно

43
Нет, это не так - он разбивается на код в точке непосредственно перед выдачей исключения. Если у вас есть попытка / поймать где-то еще в вашем коде, вы можете даже не заметить исключение!
Марк Ингрэм

2
+1 У меня было много проблем, когда люди просто пытались / ловили исключения, ничего не делая, поэтому отслеживание ошибок было проблемой
dance2die

5
Я предполагаю, что есть случаи, когда вы можете захотеть сделать это, но вы никогда не должны ловить общее исключение!
Casebash

8
@MarkIngram -1 на ваш ответ и +1 на ваш комментарий, оправдывающий его. Это хороший трюк для определенных специфических обстоятельств, но это кажется странной вещью, которую нужно сделать вообще для всей проверки.
Марк Эмери

24

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

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

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

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

В итоге,

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

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

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


1
+1 за указание на принципиальное различие между Debug.Assert и Trace.Assert, поскольку OP специально спрашивал о производственном коде.
Джон Кумбс

21

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

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


11

Коротко

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

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

Утверждения имеют огромное преимущество:

  • Чтобы помочь в поиске недостающей проверки пользовательских входных данных или ошибок верхнего уровня в коде более высокого уровня.
  • Утверждения в кодовой базе четко передают предположения, сделанные в коде, читателю
  • Утверждение будет проверено во время выполнения в Debug сборках.
  • После того, как код будет полностью протестирован, перестройка кода в качестве Release устранит накладные расходы производительности при проверке предположения (но с тем преимуществом, что более поздняя сборка 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 - Устанавливает статическую проверку при вызове методов, не оформленных в соответствии с Контрактом.

К сожалению, Code Contracts практически умерли, так как MS прекратила его разработку.
Майк Лоури

10

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

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

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

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


4
Ваш ответ заслуживает некоторой заслуги, хотя, как вы подчеркиваете, некоторые из них часто вызывают обеспокоенность, тот факт, что они прерывают сеанс отладки и вероятность ложного срабатывания. Однако вы упускаете некоторые тонкости и пишете «Оптимизировать утверждения», что может быть основано только на том, что выдает исключение и выполняет debug.assert. Это не так, они служат разным целям и характеристикам, как вы можете видеть в некоторых ответах с голосованием. Dw
Каспер Леон Нильсен

+1 за «Что мне не нравится, так это то, что отладочная сборка функционально отличается от сборки релиза. Если отладочное завершение не удается, но функциональность работает в релизе, то как это имеет смысл?» В .NET System.Diagnostics.Trace.Assert()выполняется как в сборке Release, так и в сборке Debug.
DavidRR

7

Согласно стандарту IDesign , вы должны

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

using System.Diagnostics;

object GetObject()
{...}

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

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


Похоже, Ювал Лоуи любит цитировать себя.
devlord

6

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

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


В .NET можно использовать System.Diagnostics.Trace.Assert()утверждение для выпуска (производственной) сборки.
DavidRR

Правило анализа кода CA1062: проверка аргументов открытых методов требует проверки аргумента, nullкогда: «видимый извне метод разыменовывает один из своих ссылочных аргументов, не проверяя, является ли этот аргумент нулевым ». В такой ситуации метод или свойство должны быть выброшены ArgumentNullException.
DavidRR

6

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

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!


1
Итак, в третьем случае, что произойдет, если en == nullв производстве? Возможно, вы говорите, что en == nullэто никогда не произойдет в производстве (так как программа была тщательно отлажена)? Если это так, то, Debug.Assert(en != null)по крайней мере, служит альтернативой комментарию. Конечно, если будущие изменения будут сделаны, это также будет иметь значение для обнаружения возможной регрессии.
DavidRR

@DavidRR, я действительно утверждаю, что оно никогда не может быть нулевым, как и утверждение в коде, отсюда и название. Конечно, я могу ошибаться или ошибаться из-за изменений, и в этом ценность вызова assert.
Джон Ханна

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

@DavidRR, который делает это уместным только тогда, когда я считаю, что это не может произойти, поскольку опять-таки это утверждение факта, а не проверка состояния. Конечно, также бессмысленно, если иметь утверждение, иметь ошибку, которую он может поймать, и при этом никогда не сталкиваться с этим случаем при тестировании.
Джон Ханна

4

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

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

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

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

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

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

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

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

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

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

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


4

Цитата, взятая у прагматичного программиста: от подмастерье до мастера

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

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

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

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

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

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

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


2

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

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


1
Нет, как и некоторые другие ответы здесь: вы действительно не понимаете разницу, поэтому вы отказываетесь от одного из предложений, устанавливая ложную дихотомию между ними в процессе. Dw
Каспер Леон Нильсен

3
Это единственный правильный ответ в этом списке ИМО. Не отвергай это так легко, Каспер. Отладка Assert является анти-паттерном. Если его инвариант во время отладки, его инвариант во время выполнения. Разрешение вашему приложению продолжать работу со сломанным инвариантом оставляет вас в недетерминированном состоянии и потенциально может вызвать проблемы хуже, чем сбой. IMO лучше иметь один и тот же код в обеих сборках, которые быстро терпят неудачу с нарушенными контрактами, а затем реализуют надежную обработку ошибок на верхнем уровне. например, изолировать компоненты и реализовать возможность их перезапуска (например, сбой вкладки в браузере не приводит к краху всего браузера).
justin.m.chase

1
Возможно, было бы полезно включить Trace.Assert в ваше обсуждение здесь, так как он не может быть отклонен тем же аргументом.
Джон Кумбс

0

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


2
извините, но исключения делают все то же самое, поэтому этот ответ не касается реального вопроса.
Роман Старков

0

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

В первом случае утверждения даже не должны быть в конечном коде. Вы должны использовать 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>

-1

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


-1

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.