Пользователь
0,0
рейтинг
30 марта 2012 в 19:41

Разработка → Assert. Что это? из песочницы

Assert — это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы. Эта конструкция может автоматически сигнализировать при обнаружении некорректных данных, что обычно приводит к аварийному завершению программы с указанием места обнаружения некорректных данных. Странная, на первый взгляд, конструкция — может завалить программу в самый неподходящий момент. Какой же в ней смысл? Давайте подумаем, что произойдет, если во время исполнения программы в какой-то момент времени некоторые данные программы стали некорректными и мы не «завалили» сразу же программу, а продолжили ее работу, как ни в чем не бывало. Программа может еще долго работать после этого без каких-либо видимых ошибок. А может в любой момент времени в будущем «завалиться» сама по известной только ей причине. Или накачать вам полный винчестер контента с гей-порносайтов. Это называется неопределенное поведение (undefined behavior) и, вопреки расхожему мнению, оно свойственно не только языкам программирования с произвольным доступом к памяти (aka C, C++). Т.к. assert завершает программу сразу же после обнаружения некорректных данных, он позволяет быстро локализировать и исправить баги в программе, которые привели к некорректным данным. Это его основное назначение. Assert'ы доступны во многих языках программирования, включая java, c#, c и python.
Какие виды assert'ов бывают?
Assert'ы позволяют отлавливать ошибки в программах на этапе компиляции либо во время исполнения. Проверки на этапе компиляции не так важны — в большинстве случаев их можно заменить аналогичными проверками во время исполнения программы. Иными словами, assert'ы на этапе компиляции являются ничем иным, как синтаксическим сахаром. Поэтому в дальнейшем под assert'ами будем подразумевать лишь проверки во время исполнения программы.

Assert'ы можно разделить на следующие классы:
  • Проверка входящих аргументов в начале функции.

Если найдено недопустимое значение какого-либо аргумента, значит, где-то рядом с местом вызова этой функции могут быть баги. Пример:
// Считает факториал числа n.
// Число n должно лежать в пределах от 0 до 10 включительно.
int factorial(int n)
{
  // Факториал отрицательного числа не считается
  assert(n >= 0);

  // Если n превысит 10, то это может привести либо к целочисленному
  // переполнению результата, либо к переполнению стэка.
  assert(n <= 10);

  if (n < 2) {
    return 1;
  }

  return factorial(n - 1) * n;
}

// мы 'забыли' об ограничениях функции factorial() и пытаемся вычислить
// факториалы чисел от 0 до 99.
//
// проверка внутри factorial() любезно напомнит нам о своих ограничениях,
// так что мы сможем быстро выявить и исправить этот баг.
//
// если бы эта проверка отсутствовала, то баг мог бы долго оставаться
// незамеченным, периодически давая о себе знать переполнениями стэка и
// некорректным поведением программы.
for (int i = 0; i < 100; ++i) {
  a[i] = factorial(i);
}

Что такое целочисленное переполнение.
Что такое переполнение стэка.

Важно понимать, что входящие аргументы функции могут быть неявными. Например, при вызове метода класса в функцию неявно передается указатель на объект данного класса (aka this и self). Также функция может обращаться к данным, объявленным в глобальной области видимости, либо к данным из области видимости лексического замыкания. Эти аргументы тоже желательно проверять с помощью assert'ов при входе в функцию.
Если некорректные данные обнаружены на этом этапе, то код данной функции может содержать баги. Пример:
int factorial(int n)
{
  int result = 1;

  for (int i = 2; i <= n; ++i) {
    result *= i;
  }

  // С первого взгляда эта проверка никогда не сработает - факториал должен
  // быть всегда положительным числом. Но как только n превысит допустимый
  // предел, произойдет целочисленное переполнение. В этом случае
  // a[i] может принять отрицательное либо нулевое значение.
  //
  // После срабатывания этой проверки мы быстро локализуем баг и поймем,
  // что либо нужно ограничивать значение n, либо использовать целочисленную
  // арифметику с бесконечной точностью.
  assert(result > 0);

  return result;
}


Что такое арифметика с произвольной точностью.
Результат функции может быть неявным. Например, функция может модифицировать данные, на которые ссылаются (напрямую или косвенно) аргументы функции. Также функция может модифицировать данные из глобальной области видимости или из области видимости лексического замыкания. Корректность этих данных желательно проверять перед выходом из функции.
  • Проверка данных, с которыми работает функция, внутри кода функции.
Если в середине функции обнаруживаются некорректные данные, то баги могут быть где-то в районе этой проверки. Пример:
int factorial(int n)
{
  int result = 1;

  while (n > 1) {
    // Знакомая нам проверка на целочисленное переполнение.
    //
    // При ее срабатывании мы быстро определим, что эта функция должна уметь
    // корректно обрабатывать слишком большие n, ведущие к переполнению.
    //
    // Эта проверка лучше, чем проверка из предыдущего пункта (перед выходом
    // из функции), т.к. она срабатывает перед первым переполнением result,
    // тогда как проверка из предыдущего пункта может пропустить случай, когда
    // в результате переполнения (или серии переполнений) итоговое значение
    // result остается положительным.
    assert(result <= INT_MAX / n);

    result *= n;
    --n;
  }

  return result;
}


Когда и где стоит использовать assert'ы?
Ответ прост — используйте assert'ы всегда и везде, где они хоть чуточку могут показаться полезными. Ведь они существенно упрощают локализацию багов в коде. Даже проверка результатов выполнения очевидного кода может оказаться полезной при последующем рефакторинге, после которого код может стать не настолько очевидным и в него может запросто закрасться баг. Не бойтесь, что большое количество assert'ов ухудшит ясность кода и замедлит выполнение вашей программы. Assert'ы визуально выделяются из общего кода и несут важную информацию о предположениях, на основе которых работает данный код. Правильно расставленные assert'ы способны заменить большинство комментариев в коде. Большинство языков программирования поддерживают отключение assert'ов либо на этапе компиляции, либо во время выполнения программы, так что они оказывают минимальное влияние на производительность программы. Обычно assert'ы оставляют включенными во время разработки и тестирования программ, но отключают в релиз-версиях программ. Если программа написана в лучших традициях ООП, либо с помощью enterprise методологии, то assert'ы вообще можно не отключать — производительность вряд ли изменится.

Когда можно обойтись без assert'ов?
Понятно, что дублирование assert'ов через каждую строчку кода не сильно улучшит эффективность отлова багов. Не существует единого мнения насчет оптимального количества assert'ов, также как и насчет оптимального количество комментариев в программе. Когда я только узнал про существование assert'ов, мои программы стали содержать 100500 assert'ов, многие из которых многократно дублировали друг друга. С течением времени количество assert'ов в моем коде стало уменьшаться. Следующие правила позволили многократно уменьшить количество assert'ов в моих программах без существенного ухудшения в эффективности отлова багов:
Можно избегать дублирующих проверок входящих аргументов путем размещения их лишь в функциях, непосредственно работающих с данным аргументом. Т.е. если функция foo() не работает с аргументом, а лишь передает его в функцию bar(), то можно опустить проверку этого аргумента в функции foo(), т.к. она продублирована проверкой аргумента в функции bar().
Можно опускать assert'ы на недопустимые значения, которые гарантированно приводят к краху программы в непосредственной близости от данных assert'ов, т.е. если по краху программы можно быстро определить местонахождение бага. К таким assert'ам можно отнести проверки указателя на NULL перед его разыменованием и проверки на нулевое значение делителя перед делением. Еще раз повторюсь — такие проверки можно опускать лишь тогда, когда среда исполнения гарантирует крах программы в данных случаях.
Вполне возможно, что существуют и другие способы, позволяющие уменьшить количество assert'ов без ухудшения эффективности отлова багов. Если вы в курсе этих способов, делитесь ими в комментариях к данному посту.

Когда нельзя использовать assert'ы?
Т.к. assert'ы могут быть удалены на этапе компиляции либо во время исполнения программы, они не должны менять поведение программы. Если в результате удаления assert'а поведение программы может измениться, то это явный признак неправильного использования assert'а. Таким образом, внутри assert'а нельзя вызывать функции, изменяющие состояние программы либо внешнего окружения программы. Например, следующий код неправильно использует assert'ы:
// Захватывает данный мютекс.
//
// Возвращает 0, если невозможно захватить данный мютекс из-за следующих причин:
// - мютекс уже был захвачен.
// - mtx указывает на некорректный объект мютекса.
// Возвращает 1, если мютекс успешно захвачен.
int acquire_mutex(mutex *mtx);

// Освобождает данный мютекс.
//
// Возвращает 0, если невозможно освободить данный мютекс из-за следующих
// причин:
// - мютекс не был захвачен.
// - mtx указывает на некорректный объект мютекса.
// Возвращает 1, если мютекс успешно захвачен.
int release_mutes(mutex *mtx);

// Убеждаемся, что мютекс захвачен.
assert(acquire_mutex(mtx));

// Работаем с данными, "защищенными" мютексом.
process_data(data_protected_by_mtx);

// Убеждаемся, что мютекс освобожден.
assert(release_mutes(mtx));


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

int is_success;

is_success = acquire_mutex(mtx);
assert(is_success);

// Теперь данные защищены мютексом даже при отключенных assert'ах.
process_data(data_protected_by_mtx);

is_success = release_mutex(mtx);
assert(is_success);


Т.к. основное назначение assert'ов — отлов багов (aka ошибки программирования), то они не могут заменить обработку ожидаемых ошибок, которые не являются ошибками программирования. Например:
// Пытается записать buf_size байт данных, на которые указывает buf,
// в указанное сетевое соединение connection.
//
// Возвращает 0 в случае ошибки записи, возникшей не по нашей вине. Например,
// произошел разрыв сетевого соединения во время записи.
// Возвращает 1 в случае успешной записи данных.
int write(connection *connection, const void *buf, size_t buf_size);

int is_success = write(connection, buf, buf_size);

// "Убеждаемся", что данные корректно записаны.
assert(is_success);


Если write() возвращает 0, то это вовсе не означает, что в нашей программе есть баг. Если assert'ы в программе будут отключены, то ошибка записи может остаться незамеченной, что впоследствие может привести к печальным результатам. Поэтому assert() тут не подходит. Тут лучше подходит обычная обработка ошибки. Например:
while (!write(connection, buf, buf_size)) {
  // Пытаемся создать новое соединение и записать данные туда еще раз.
  close_connection(connection);
  connection = create_connection();
}


Я программирую на javascript. В нем нет assert'ов. Что мне делать?
В некоторых языках программирования отсутствует явная поддержка assert'ов. При желании они легко могут быть там реализованы, следуя следующему «паттерну проектирования»:

function assert(condition)
{
  if (!condition) {
    throw "Assertion failed! See stack trace for details";
  }
}

assert(2 + 2 === 4);
assert(2 + 2 === 5);


Вообще, assert'ы обычно реализованы в различных фреймворках и библиотеках, предназначенных для автоматизированного тестирования. Иногда они там называются expect'ами. Между автоматизированным тестированием и применением assert'ов есть много общего — обе техники предназначены для быстрого выявления и исправления багов в программах. Но, несмотря на общие черты, автоматизированное тестирование и assert'ы являются не взаимоисключающими, а, скорее всего, взаимодополняющими друг друга. Грамотно расставленные assert'ы упрощают автоматизированное тестирование кода, т.к. тестирующая программа может опустить проверки, дублирующие assert'ы в коде программы. Такие проверки обычно составляют существенную долю всех проверок в тестирующей программе.
Павел Новик @darkpashka
карма
17,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (56)

  • +1
    В некоторых браузерах есть console.assert:
    console.assert(2 + 2 === 4, "Something really bad happened to numbers!")
  • 0
    Пожалуйста, используйте тег
    .
    • +1
      Тег _<_source_>_
      • +1
        поправил, спасибо
  • +1
    Assert'ы могут ещё выполнять роль документирования кода. Например декларировать ожидаемые диапазоны значений переменных более узкие, чем стандартные типы.
  • 0
    В .net во всех тестовых Framework используется Assert для проверки возвращаемых значений и пост условий.

    Для самого же исходного кода программы в .net привычнее использовать Code.Contracts… Там есть и пост условия и предусловия и проверка в самом теле метода…

    Есть конечно Debug.Assert, но я таких примеров мало видел.

    А Вообще, была такая книжка- отладка приложений под Windows8 года так 2004 вроде, так вот там целая глава про эти Assert на С++
    • +2
      windows 8 в 2004 то году? :)
      • +2
        Я думаю речь о книжке Джона Роббинса Отладка приложений Microsoft .NET и Microsoft Windows. Очень стоящая книжка, must read. Microsoft её, кстати, раздавала в составе «боекомплекта разработчика» какое-то время тому назад.

        А про Windows8 просто опечатка.
  • +5
    @ автору:
    На мой взгляд у Вас немного путаница в терминологии.
    1)
    assert — это не просто условие во время исполнения программы, но прежде всего инструмент отладки. По этой причине в С++ в релизных билдах их уже нет, да и в других языках к примеру в Python assert
    2)
    То что Вы называете assert-ом проверки входных и выходных данных в статье на мой взгляд не правильно, лучше придерживаться мнения Страуструпа. Он называет это «пре и пост условия», а проверку значений в цикле «инвариантом» цикла.

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

    ИМХО
    • 0
      п.1 снимаю, его вы описали другими словами почти в самом конце статьи. А ведь знание о том что assert не должен попадать в релизный билд очень важно!
      • 0
        я думаю релизный билд и ситуация с кастомером имеют одну природу, которая описана в следующем комментарии. Согласен с этим конечно же.
        • 0
          Может чуть улучшите статью, чтобы можно было ссылаться? )
      • –1
        Ассерт не должен срабатывать в релизном билде, потому что в этом билде не должно быть ошибок, от которых он защищает. Но если ошибка есть, то лучше пусть ассерт её поймает, чем она набедокурит сама. Ассерты из кода релизных билдов выключать ВРЕДНО.
        • 0
          1)
          Еще раз, assert это не простое условие, а условие предназначенное для того чтобы быстро и мгновенно сказать программеру о том, что он не правильно понимает задачу. Т.е. инструмент поддержки продукта, когда к разработчику приходят и говорят о баге, тут он быстро достает дебажный билд и выясняет все что только можно.
          То о чем вы говорите про «набедокурит» это runtime и logic ошибки. И это совсем из другой «оперы» ошибки.
          2)
          Посмотрим с другого угла зрения. assert — пишут с такой же целью что и unit-тесты. Вы же не включаете в продукт код unit-тестов? Лично я нет. Могу только иногда в отдельной папке ложить, если это библиотека.
          Если у вас продукт хорошо обложен unit-тестами, то вы можете вообще не употреблять assert-ами.
          3)
          Как бы разработчик не старался весь код проверками невозможно обложить, ровно как и все и вся затестировать нельзя. Обложить все и вся проверками не получится, т.к. задачи и требования к продукту могут меняться. Поэтому код нужно обкладывать разумными проверками, который постарается правильно упасть, тем самым дать разработчику достаточную для поддержки информацию или упасть так, чтобы не привести к плачевным последствиям.

          P.S.:
          На сайте мелкософта можно закачать дебажное ядро Windows, попробуйте поработать с ним. В нем очень много assert-ов. Думаю вы поймете, когда надо, а когда не надо применять assert!
          • 0
            • 0
              По ссылке указанной вами комент за «31 марта 2012, 16:42», а я же ответил на «31 марта 2012, 16:25». Не заметили? Вот последний как раз содержит весьма плохо-пахнущую практику «Ассерты из кода релизных билдов выключать ВРЕДНО». Перечитайте внимательней мой коммент.
              • 0
                там пример, подтверждающий моё утверждение, и отчасти опровергающий Ваше.
                • +1
                  Вы что курите?

                  В одном комментарии Вы пишете:
                  >>31 марта 2012, 16:25
                  >>Ассерты из кода релизных билдов выключать ВРЕДНО

                  А в другом:
                  >>31 марта 2012, 16:42
                  >>Считаете, кастомеру больше понравится неопределённое поведение? Особенно он будет счастлив, когда Вы не сможете его локализовать и исправить.

                  То что кастомеру больше понравиться… ежу понятно и это даже не обсуждается именно за тем и работает программер, что меньше парить мозг кастомеру. С этим ни кто не спорит!

                  Ваше слова про ассерт, что их нужно оставлять в релизе говорят о том что Вы не совсем понимаете смысл и цель существования assert!!! Вы путаете понятие assert c проверкjq, которая проверяет что-то в коде и в случае чего правильно валит программу либо еще что-то!
                  Обратите внимание на перевод слова «assert» с англ. на русский. «утверждаю». Это своего рода документация на исходный код потомкам «Утверждаю что в этом месте не должно быть....». Это совсем другое и отличное от привычных нам конструкций в коде на вроде: «Если вдруг значение переменной икс больше… тогда покажем багрепорт».
                  • 0
                    Приведу одну из ситуаций, где нужно использовать assert. К примеру Вы только вчера вышли на новую работу и Вам дали проект с большим количеством строк. В проекте Вы еще ничего не знаете и только еще приступили разбираться. Перед Вами поставлена задача за 1-2 дня поправить багу и выкатить новый update. В ходе работы с кодом и его отладкой вы вдруг находите багу. Допустим она в неком хитро-вывернутом цикле с некисло написанным коде, который не мешало бы отрефакторить. Цикл помещен в функцию которая очень часто вызывается и тем самым влияет на эффективность работы приложения, к примеру подбор пароля.

                    Каковы Ваши действия?

                    На мой взгляд такие варианты:
                    1) Просто внести изменения в код, тем самым поправить багу. Выкатить обновление. Отрапортовать о выполнении задачи шефу.
                    2) Вы непросто фиксаете код, но и попутно пишите unit-тест, который в будущем позволит Вам или Вашим коллегам проверить этот цикл на корректность работы.
                    3) Вы фиксаете код и пишите assert.

                    Выводы:
                    1) Если идти по варианту №1 то в будущем вы можете забыть работу этого цикла и снова придется вникать
                    2) Если же идти по варианту №2, то Вы можете не уложиться в отведенные на задачу 1-2 дня!!!
                    3) Самое наилучшее решение, этим самым вы говорите самому себе в будущем и своим коллегам «Утверждаю что в этом коде переменная1 не должна быть больше чем переменная2», ну или еще чтото.

                    Если вы пойдете по варианту №3, то выкатив на рынок обновление ПО Вы никак не ухудшите его работу по скорости, т.к. лишней проверки assert нету в релизе.
                    Однако при этом в случае проблем Вы имеет возможность скомпиллировать дебажную версию и получить так нужные вам assert-ы, которые помогают вам понять докопаться до проблемы
  • +2
    На мой взгляд, статья не полностью покрывает весь спектр того, что стоит использовать при программировании в энтерпрайзе. Вот представим, что у нас продукт, который стоит у сотни тысяч кастомеров и одна ошибка будет дорого стоить. Что тогда? Согласны ли вы, чтобы ваш любимый софт иногда падал с ассертом в каком-то специфическом сценарии. Да, конечно, с точки зрения прогаммиста все пучково — есть баг, ассерт его поймал вовремя, вот колстек, вперед. Попробуйте это объяснить кастомеру. А кастомер этот какой-нибудь крупный банк или страховщик. Что он скажет? Скажет: вот ассерт у вас тут сработал, вот вам колстек, коредамп, молодцы мужики, что не оставили неопределенное поведение. Да? Сомневаюсь. С точки зрения программиста ассерты — это добро, с точки зрения кастомера — зло. Поэтому правильный подход — использование исключений и тотальный контроль и обработка ошибок. Любой ассерт должен быть продублирован обработкой ошибок. А самые классные ассерты — это те, которые кидают исключение при срабатывании. Но для этого необходимо транзакционное поведение на любом уровне. Вот этого я, к сожалению, не увидел в статье.
    • 0
      Не спорю, но покрыть всё не так уж и сложно. Насчёт ситуации с кастомером также имею схожее мнение.
      • 0
        *не так уж и просто
    • +1
      Считаете, кастомеру больше понравится неопределённое поведение? Особенно он будет счастлив, когда Вы не сможете его локализовать и исправить.
      • 0
        Считаю, что кастомеру ничего не понравится. Ему локализация бага неинтересна, он уже теряет деньги на этом баге. Ему интересно, когда он перестанет их терять. Поэтому тут все несколько сложнее. По хорошему, ассерты должны быть продублированы правильным «еррор-хендлингом», т.е. обработкой ошибок. Именно так рекомендуют делать для серьезных программ.
        • 0
          несомненно
    • 0
      Тут два утверждения, которые противоречат друг другу: «показ assert'ов — это плохо» и «делайте assert'ы на исключениях с транзакционным поведением и обработкой ошибок» (обработка = показ пользователю и восстановление работы?).

      Имхо, перехватив исключение, нужно всё-таки показать пользователю ошибку и не пытаться её маскировать. Неопределённое поведение всем выйдет дороже, а протестировать всё — утопия. И да, исключения и транзакционность — это правильно, +1.
  • +1
    Извините, но накачать полный винчестер контента с гей-порносайта — это вполне определенное поведение.
    • +1
      Ассерты — это способ описания/документации невозможной ситуации. Невозможной, при условии, что написанный код будет использоваться правильно. То есть ассерты — они для программистов, для более раннего нахождения грубых ошибок в коде, для того, чтобы обратить внимание других программистов на обязательные предусловия/постусловия. В идеале все должно быть так, чтобы для конечного пользователя поведение было абсолютно одинаковое, что с включенными ассертами, что с выключенными. А обработка ошибок, диагностика, core dumps, etc в готовом продукте должны выполняться другими средствами.
    • 0
      Нет, если она может его накачать или не накачать в зависимости от положения звезд.
  • +3
    Как раз недавно с товарищем обсуждали использовать ли assert'ы.

    1. Мне кажется в этом примере:
    …
    is_success = acquire_mutex(mtx);
    assert(is_success);
    
    // Теперь данные защищены мютексом даже при отключенных assert'ах.
    process_data(data_protected_by_mtx);
    …
    

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

    2. Вариант для JS какой-то бессмысленный — зачем делать функцию кидания одинаковых исключений, когда лучше в каждом конкретном случае кидать что-то своё, чтоб видеть что именно отвалилось без копания в стектрейсе?
    • –1
      Алгоритм, вылавливания ошибки:
      1) Изучаешь функцию, в которой произошел сбой, на предмет ошибок.
      2) Если ошибка не обнаружена, то находишь все места вызова функции, в которой произошел сбой. Для каждого найденого места вызова функции инспектируешь код, который в последний раз модифицировал данные, на которых произошел сбой.
      3) Если источник ошибки не найден, рекурсивно просматриваешь места вызова функций, из которых была вызвана наша функция.
      4) Если задолбало инспектировать код функций, то добавляешь недостающие assert'ы и/или trace-сообщения, которые бы позволили более точно локализировать ошибку, пересобираешь программу и предлагаешь ее пользователю :)
      5) Если это не помогло, то добавляешь в программу возможность автоматической отправки крэшдампа на наш сервер, и отсылаешь пересобранную программу пользователю :)
      Грамотно расставленные assert'ы, находящиеся в районе инспектируемых функций и проверяющие сбойные данные, позволяют эффективно отсечь из рассмотрения функции, находящиеся выше по стэку. В идеале баг находится на либо на первом, либо на втором шаге.
  • 0
    Т.е. мы заменяем полноценные тесты дополнительными проверками в коде (которые или выкидывают исключение, или завершают программу с ошибкой). Не легче ли снабдить исходные коды папочкой tests и и сключать эту папку из финальной сборки?
    • +1
      Нет, ассерт это не тест.
      Тест проверяет результат программы при заданных входных параметрах:
      (2 * 2 == 4)
      Ассерт проверяет инварианты хода выполнения программы и никак не зависит от входных данных:
      (2 * 3 == 3 * 2)
      • +1
        Лучше так:
        (x * y == y * x)
    • 0
      Ассерты и тесты скорее взаимодополняющие инструменты в этом применении.

      Тестами мы проверяем, что получаем правильный результат при корректных параметрах, а ассертами, что всегда (по крайней мере во время выполнения тестов и просто запусков дев-билдов) получаем корректные параметры.
      • –1
        извини, мимо плюса промахнулся.
  • +1
    > Когда нельзя использовать assert'ы?
    В деструкторах C++ объектов.
    Вылет исключения при размотке стека (в деструкторе автоматического объекта) из-за срабатывания другого исключения (например, проверка как-то условия) приводит к аварийному завершению изделия вообще без диагностики.
    • 0
      Assert-ы не всегда бросают исключения. Например, в Qt Q_ASSERT останавливает отладчик и выводит ошибку в консоль.
      • 0
        Q_ASSERT вызывает qFatal(), а qFatal() может и программу завершить если не установить свой message handler.
        • 0
          Это понятно, но в деструкторах его без проблем можно использовать, диагностику это не затрудняет.
      • 0
        Кстати, да — полезная в реализации (своего) assert'а функция: ::DebugBreak() — иммитирует break-point и останавливает отладчик в месте вызова. На других платформах, думаю, есть что-то похожее.
  • +1
    Пусть меня заминусуют, но эта статья — восторженно-рекламный бред дилетанта. Чушь, заблуждения или ошибки практически в каждом абзаце.
    • 0
      Восторженно-рекламный бред дилетанта — это писать комментарии не аргументируя своё мнение.
      • 0
        другие, не столь категоричные комменты, часть проблем поста уже обозначили. Вам мало? доберусь до ББ — выпишу ещё.

        а к «писать комментарии не аргументируя своё мнение» ни один из скопированных Вами эпитетов не подходит.
  • +3
    Категорически с Вами не согласен с утверждением: «Проверки на этапе компиляции не так важны»
    Только не для компилируемого языка со строгой типизацией, где можно многие проверки переложить на компилятор.
    Когда программу с ошибками вообще нельзя скомпилировать — это самая лучшая проверка, и самая важная.

    А насчет асертов, то как их использовать вместе с юнит тестами?
    Я когда вижу асерт в функции, то это 100% гарантия того, что это условие не проверяется юнит тестами.

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

    Мои высказывания были по поводу асертов в C++.
    • 0
      А насчет асертов, то как их использовать вместе с юнит тестами?
      Я когда вижу асерт в функции, то это 100% гарантия того, что это условие не проверяется юнит тестами.


      Допустим есть условие, что функция (сложная) не должна возвращать NULL. Как вы будете проверять это юнит-тестами? В лучшем случае вы проверите несколько комбинаций параметров и убедитесь, что они возвращают ожидаемые значения. Но это не даёт гарантии, что NULL не вернётся при других комбинациях. Ассерт же гарантирует (если они включены), что функция не вернёт NULL ни при каких условиях, программа просто вылетит. Тесты, кстати, тоже не пройдут.
      • 0
        Assert, как правило, используется только в отладочной версии и ничего не гарантирует в релизной.
        А значит он бесполезен, если функция будет возвращать NULL у пользователя.
        Лучше вместо асерта использовать ислключения, если необходима гарантированная проверка.
        (Либо не отключать асерты в релизе, но тогда это уже не асерты, а исключения)

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

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

        • 0
          Несколько ошибок я ловил ассертами во время функциональных тестов, именно ошибок программирования — где-то опечатался, где-то ещё что-то в этом роде. И не думал их использовать для ловли ожидаемых ошибок, таких как валидация пользовательского ввода или взаимодействия с внешними системами. Вот дескриптор уже открытого файла, передаваемый скажем в функцию чтения, внтури неё проверить можно ассертом. Он не должен быть NULL по логике приложения никогда. Если не открылся, то исключение должно выскочить раньше вызова функции, но вдруг где-то что-то упустил…
        • 0
          Ассерты в релизе — всё равно ассерты, потому что ассерты — это исключения, касающиеся нарушений предусмотренных их автором соглашений относительно каких-либо значений переменных в соответствующих местах алгоритмов. И не более того.
  • 0
    Asset выглядит «костылём», что ли, если используется только проверки корректности входных параметров и инваринта класса. Для этого есть контрактное программирование (Design By Contract).
    • +1
      дык асерты и есть один из инструментов реализации контрактного программирования, разве нет?
    • 0
      Контракты в синтаксисе по сути лишь сахар для ассертов.
  • +1
    Хочу заметить, что использовать ассерты для проверки входных данных public-функции — ошибка. Функция должна проверять аргументы в любом случае и корректно реагировать (либо бросать исключение, либо возвращать специальное значение, сигнализирующее о недопустимых значениях аргументов). Assert для проверки входных данных можно использовать только в private-методах (в том случае, когда вы точно знаете, с какими аргументами вызываете свой метод). Потому что срабатывание assert рассматривается как ошибка В КОДЕ ФУНКЦИИ а не в вызывающем коде, и если вы оставите public функцию с ассертом на входные данные, то потом будете лезть в функцию и искать ошибку там, где её нет, вместо того, чтобы проанализировать ArgumentException.
    • 0
      срабатывание assert рассматривается как ошибка В КОДЕ ФУНКЦИИ а не в вызывающем коде

      Проверка assert-ом входящих параметров к такому определению не относится.
  • 0
    А ещё можно писать так
    assert instance != null: «Instance is null!»;
    без скобок и со своим сообщением
    • 0
      Не особо наглядно. Если говорить про С, то лучше применять классическую конструкцию:
      assert( instance != null && "Instance is null!" );
      

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