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

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

Например,

void f(int *p)
{
  assert(p);
  p->do();
}

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

void f(int *p)
{
  assert(p);

  if (p)
  {
    p->do();
  }
}

В конце концов, утверждение означает, что условие, которое оно проверяет, НИКОГДА не должно быть ложным. Но если, если мы его не проверим, и он выйдет из строя, программа выйдет из строя. Похоже на дилемму. Как вы, ребята, с этим справляетесь?


person Eric Z    schedule 29.10.2010    source источник
comment
Дубликат дизайна путем проверки контракта с помощью утверждения или исключения? (Там много хороших дискуссий о плюсах и минусах разных подходов; на самом деле нет единого мнения по этому поводу) См. также Когда утверждения должны оставаться в рабочем коде?   -  person James McNellis    schedule 29.10.2010
comment
Не думаю, что когда-либо видел, чтобы на вопрос было так много ответов за такое короткое время.   -  person Mark Ransom    schedule 29.10.2010
comment
Ага, и все друг другу противоречат :)   -  person EboMike    schedule 29.10.2010
comment
@ Джеймс: спасибо, я посмотрю на эту ссылку :)   -  person Eric Z    schedule 29.10.2010


Ответы (8)


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

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

Если вы находитесь в лагере, который говорит: «А что, если утверждения не работают? Мне нужно их поймать!» тогда вы упускаете суть. В таком случае спросите себя: почему вы просто не генерируете исключение (или не обрабатываете ошибку другим способом)?

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

person user359996    schedule 29.10.2010
comment
Обратите внимание, что в моем ответе явно не рассматривается вопрос о том, следует ли включать утверждения в производственные выпуски (они не должны), поскольку это выходит за рамки вопроса. - person user359996; 29.10.2010

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

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

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

person EboMike    schedule 29.10.2010
comment
Вверх. Я согласен с вашей идеей, что иногда лучше сбой программы с хорошим уведомлением пользовательского интерфейса, чем позволить ей работать в плохом состоянии без предварительного уведомления. - person Eric Z; 29.10.2010
comment
@Eric: Если вы можете определить, что программа находится в плохом состоянии, вы можете обработать это соответственно (например с уведомлением пользовательского интерфейса). В этом случае assert не требуется, так как выдача исключения или включение кода обработки ошибок вполне подойдут. Более того, если вы не на 100% уверены, что для программы невозможно находиться в таком состоянии, assert не является правильной конструкцией - вам, опять же, следует , просто проверьте состояние и либо вызовите исключение, либо обработайте ошибку. Утверждение не говорит, что я надеюсь, что это правда, он говорит, что я утверждаю, что это (обязательно) правда. - person user359996; 29.10.2010
comment
Вы можете обнаружить плохое состояние, но вы не можете исправить его. Если у переменной есть значение, которого она, возможно, не должна иметь, как вы от этого избавитесь? Угадайте правильное значение? Это признак того, что что-то еще может быть полностью сломано, что приведет к тому, что эта переменная станет плохой. Может, даже память разгромлена. Вы не можете оправиться от этого. Кроме того, из соображений производительности вы не хотите тратить слишком много времени на проверку ошибок в сборке выпуска. (Мои требования, вероятно, отличаются от ваших, я в основном занимаюсь играми - там каждая ветка учитывается в некоторых основных функциях.) - person EboMike; 29.10.2010
comment
@ user359996: Если assert не произойдет на 100%, какая польза от его сохранения в сборке релиза? - person Eric Z; 29.10.2010
comment
@EboMike: Я согласен с большинством ваших пунктов, но я не уверен на 100%, какую позицию вы отстаиваете. Вы предлагаете оставить утверждения в производственном коде или нет? - person user359996; 29.10.2010
comment
@ user359996: Я имею в виду, если мы на 100% уверены, что утверждения на самом деле не появятся в сборке релиза, можем ли мы просто удалить их перед выпуском? - person Eric Z; 29.10.2010
comment
@ Эрик: Отличный вопрос. У большинства (всех?) Компиляторов есть параметр (обычно включенный по умолчанию) для отключения логики утверждения для производственных выпусков. То есть вам не нужно явно удалять их из исходного кода. - person user359996; 29.10.2010
comment
@Eric: И чтобы быть предельно ясным, я действительно рекомендую отключать утверждения для производственных выпусков. - person user359996; 29.10.2010
comment
@ user359996: Извлеченный урок заключается в том, что я всегда должен использовать отладочную версию двоичных файлов на этапе разработки, что я почти забыл сделать раньше, иначе assert просто потеряет свое использование ... - person Eric Z; 29.10.2010
comment
@user \ d +: я думаю, что в одном мы можем согласиться с тем, что утверждения должны компилироваться в производственных сборках (большинство API уже настроены для этого). Я не возражаю против быстрого NULL, если проверяет работоспособность, если что-то, являющееся NULL, не имеет последствий, но кроме этого, я бы съел исключение и имел глобальный обработчик неперехваченных исключений, который вызывает графический интерфейс. - person EboMike; 29.10.2010

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

person Ignacio Vazquez-Abrams    schedule 29.10.2010

Строго говоря, второй код имеет избыточность.

void f(int *p)
{
  assert(p);
  if (p)    // Beats the purpose of assertion
  {
    p->do();
  }
}

Утверждение означает, что произошла ошибка. Что-то неожиданное / необработанное. В приведенном выше коде либо

1) Вы правильно обрабатываете случай, когда p равно нулю. (не вызывая p-> do ()) - что якобы является правильным / ожидаемым действием. Однако тогда это утверждение является ложной тревогой.

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

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

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

person Community    schedule 29.10.2010

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

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

person Mark Ransom    schedule 29.10.2010
comment
Так нужно ли мне удалять утверждения в режиме выпуска, если они тщательно протестированы? Или просто оставьте их там на случай будущих изменений другим разработчиком, поддерживающим код? - person Eric Z; 29.10.2010
comment
@Eric, обычно вы используете макрос assert, который автоматически компилируется в пустой блок кода, когда вы создаете его для выпуска. Тогда нет никаких причин избавляться от них, они будут полезны любому, кто модифицирует код. Они тоже действуют как документация. - person Mark Ransom; 29.10.2010

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

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

void f(int *p)
{

  if (!p)
  {
    do_error("FATAL, P is null.");
  }

  p->do();
}

Где do_error - это функция, которая регистрирует ошибку и полностью завершает работу.

person OmnipotentEntity    schedule 29.10.2010

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

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

person H. den Breejen    schedule 29.10.2010

Поскольку многие люди комментируют перевод утверждений в режим выпуска:

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

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

person Community    schedule 29.10.2010