Где следует использовать assert () в C соответственно? C ++? - PullRequest
22 голосов
/ 02 февраля 2012

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

Ответы [ 4 ]

36 голосов
/ 02 февраля 2012

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

Принцип.

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

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

Пример:

char const* strstr(char const* haystack, char const* needle) {
  assert(haystack); assert(needle);

  // ...
}

Альтернативы.

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

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

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

Где НЕ использовать.

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

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

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

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

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

  • Никогда не оставляйте подтверждения в Release.

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

Где его использовать.

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

  • Используйте его во время циклов разработки и тестирования.

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

Примечание: вы также должны проверить двоичный файл Release, если только для проверки производительности.

А в выпуске?

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

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

8 голосов
/ 02 февраля 2012

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

И assert, и static_assert выполняют аналогичные функции. Допустим, у вас есть какая-то функция foo. Например, допустим, у вас есть функция foo(void*), которая предполагает, что ее аргумент не равен нулю:

void foo(void* p) {
  assert(p);
  ...
}

В вашей функции есть пара человек, которые заботятся об этом.

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

Второй (и более важный) разработчик, который читает ваш код. Для него ваш утверждающий говорит, что после этой строки p не равно нулю . Это то, что иногда упускается из виду, но я считаю, что это самая полезная функция макроса assert. Он документирует и обеспечивает соблюдение условий.

Вы должны использовать assert s для кодирования этой информации, когда это целесообразно. Мне нравится думать об этом как о «на данный момент в коде, это правда» (и это говорит это таким образом , поэтому намного сильнее, чем комментарий). Конечно, если такое утверждение на самом деле не передает много / никакой информации, тогда оно не нужно.

4 голосов
/ 02 февраля 2012

Я думаю, что есть простое и мощное замечание:

assert () предназначен для проверки внутренней согласованности.

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

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

  • new int в порядке до его предварительных условий, поэтому, если память недоступна, бросок - это только разумный ответ. (Постусловие malloc - это «действительный указатель или NULL »)
  • Постусловием конструктора является существование объекта, инварианты которого установлены. Если он не может создать допустимое состояние, бросок - это only разумный ответ.

assert не следует использовать для вышеперечисленного. В отличие от

void sort (int * begin, int * end) {
    // assert (begin <= end); // OPTIONAL precondition, possibly want to throw
    for (int * i = begin, i < end; ++i) {
        assert (is_sorted (begin, i)); // invariant
        // insert *i into sorted position ...
    }
}

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

Короче говоря: assert относится к вещам, которые НИКОГДА не произойдут, если программа ЛОКАЛЬНО верна, исключения относятся к вещам, которые могут пойти не так, даже когда код верен.

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

2 голосов
/ 02 февраля 2012

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

void my_func( char* str )
{
   assert ( str != NULL );
   /* code */
}

Может также использоваться с функциями, которые возвращают нулевой указатель при ошибке:

SDL_Surface* screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE );
assert ( screen != NULL );

Точное сообщение об ошибке assert(), зависит от вашего компилятора, но обычно оно выглядит следующим образом:

Assertion failed: str, mysrc.c, line 5
...