LINUX.ORG.RU

Зачем нужны assert

 


0

3

Добрый день, я что-то никак не могу понять в чем преимущество assert, вот читаю статьи где написано «выявление ошибок, ко-ко-ко». Ну допустим. Есть у нас функция, которая там рассчитывает объем скачанного

int downloaded(int file_size, int downloaded ) {
    return (downloaded*100/filesize);
}
вот тут допустим с assert
int downloaded(int file_size, int downloaded) {
    assert(downloaded !=0 )
    return (downloaded*100/filesize);
}
И грохнется все а если без assert, сразу понятно, что ничего не скачалось и можно обработать код возврата -1
int downloaded (int file_size, int downloaded) {
    if (!downloaded) return -1;
    return (downloaded*100/filesize);
}



Последнее исправление: da17 (всего исправлений: 1)

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

Deleted
()

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

Gvidon ★★★★
()
Ответ на: комментарий от Gvidon

т.е. на момент отладочки все сомнительные места покрываешься assert что бы «if is_valid(something)», а затем ближе к релизу их отключаешь?

da17
() автор топика
Ответ на: комментарий от da17

Они не просто откючаются, они полностью выкидываются из кода.

Gvidon ★★★★
()

Не нужны, удаляй.

slapin ★★★★★
()

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

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

Пример конечно утрированный.

yetanother ★★
()

Уже контракты почти бороздят просторы, а ты задумался зачем нужны ассерты, которым лет 40.

Это для внутренних инвариантов, когда downloaded никогда не должно оказаться нулем.

anonymous
()

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

anonymous
()

#undef NDEBUG

anonymous
()

Причём здесь C++?

В любом случае, предлагаю мнение разработчика SQLite на эту тему. Как автору самого популярного куска кода на C, ему, думаю, можно доверять.

anonymous
()

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

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от yetanother

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

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

Подход с assert. Падаем вызывая батхерт пользователя.

da17
() автор топика
Ответ на: комментарий от da17

Например, если входной файл оказался повреждён, и продолжать работу программы невозможно.

anonymous
()
Ответ на: комментарий от da17

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

anonymous
()

С assert() ты можешь сузить область контракта у функции, так сказать. Например, есть функция:

int do_it(AbstractFooManager *mgr) {
    if (!mgr)
        return 0;
    // TODO something...
} 
Проверка на nullptr расширяет контракт, и пользователь этого кода будет полагаться на наличие этой проверки, постепенно давая коду обрасти nullptr-ами во всеъ местах.

Вариант

int do_it(AbstractFooManager *mgr) {
    assert(mgr);
    // TODO something...
} 
сужает контракт, заставляя этот кусок кода делать только то, для чего он и предназначен, а не заниматься проверками правильности состояния.

Makhno
()
Ответ на: комментарий от Makhno

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

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

da17
() автор топика

Потому, что должно быть так:

int downloaded (int file_size, int downloaded) {
    assert(downloaded < 0 );
    if (downloaded < 1) return -1;
    return (downloaded*100/filesize);
}
next_time ★★★★★
()

Слушай тех, кто сказал, что нужен для отладки, потому что если ты введёшь команду man assert, то там будет написано то же самое:

If the macro NDEBUG was defined at the moment <assert.h> was last included, the macro assert() generates no code, and hence does nothing at all. Otherwise, the macro assert() prints an error message to standard error and terminates the program by calling abort(3) if expression is false (i.e., compares equal to zero).

The purpose of this macro is to help programmers find bugs in their programs. The message «assertion failed in file foo.c, function do_bar(), line 1287» is of no help at all to a user.

Иначе говоря, ты включаешь в программу кучу assert'ов, и они работают, прерывая программу в указанных тобой местах и сообщая, в какой точке прервалась программа (эта информация полезна для программиста, но не для пользователя). Когда программа отлажена, ты просто определяешь макрос #define NDEBUG перед включением заголовка <assert.h>, и все твои assert'ы исключаются из компиляции одним махом: не надо удалять или комментировать их по всему коду. Если снова нужно что-то отладить, просто комментируешь макрос NDEBUG... Разумеется, в релизе программа не должна прерываться из-за каждой ошибки, а если её и нужно прервать, то, как правило, выведя сообщение, более понятное пользователю (т. е. не о том, что ошибка произошла в таком-то модуле на такой-то строке, а о том, что это за ошибка и что следует сделать пользователю, чтобы её устранить). Соответственно, в релизе assert'ов, за очень редким исключением, быть не должно. То же самое, в принципе, можно сделать и с помощью препроцессорных ветвлений типа

#ifndef NDEBUG
  printf("На строке такой-то в таком-то модуле произошла такая-то ошибка.\n");
  abort();
#endif

Но assert всё это делает за тебя, т. е. во всех сомнительных местах, требующих отладки, вместо 4 строк тебе надо писать только одну.

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

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

da17
() автор топика
Ответ на: комментарий от da17

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

aureliano15 ★★
()
Ответ на: комментарий от da17

что бы не писать в каждой ф-ии проверку вводных данных

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

legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от legolegs

т.е. все это на уровне опыта и интуиции? Четких формальных правил, что ловить ассертом, а что обрабатывать не существует?

da17
() автор топика
Ответ на: комментарий от da17

Ну вон тебе давали ссылку на мнение разработчиков SQLite. Но вообще да, опыт. Если есть формальные основания для уверенности в возможных состояниях программы - так тогда это можно типами выразить, тем более на c++.

legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от legolegs

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

anonymous
()
Ответ на: комментарий от da17

Да, причем не потому, что ты злой и ленивый как программист, а потому, что у программы физически нет возможности обработать ситуацию, для которой она не предназначена. Ты не можешь продолжать двоичный поиск, если у тебя mid вылетел за [low, high]. Или сумма углов треугольника != 2*pi. Или аргумент, куда функция возвращает ответ, внезапно NULL, хотя это единственное, ради чего ее было вызывать. И сообщать об этом некому. Это - «ошибка программирования», а не обычная ошибка в рантайме. Это тревожный звонок тебе, что алгоритм не решает задачу от слова вообще. Что он сломан неочевидным образом, и это вылезло на текущих данных. Это именно 2+2==4, и после рефакторинга это ломается постоянно, хоть ты в это пока и не веришь.

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

совсем уж крайний случай

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

anonymous
()
Ответ на: комментарий от anonymous

бросай ему объяснять, это не программист, это какой то горе менеджер или джуниор который пытается куда то устроиться
поэтому у него темы с вопросами с запашком - «а почему солнце светит ?»

anonymous
()

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

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

А в реальном мире кто во что горазд в то и использует =)

Deleted
()
Ответ на: комментарий от Deleted

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

da17
() автор топика
Ответ на: комментарий от Deleted

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

da17
() автор топика
Ответ на: комментарий от da17

Они там странные, никакого vs быть не может. Ассерты это просто повышенная строгость, тоесть твой код тупо будет падать при любой ошибке. Хандлить же надо и нужно. Просто любят многие это как его всё идеализировать и бритву Окама превращать в газонокосилку )).

Но ты меня не слушай, я вообще не программист =)

Deleted
()
Ответ на: комментарий от Deleted

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

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.