А>>Почему в большинстве библиотек которые я видел очень редко используются assert'ы? А>>Типа "настоящие" программисты ошибок не совершают...
К>Потому что assert — это самое убогое средство отладки, какое можно придумать. К>В дебаг-версии он выводит assertion failed и убивает задачу по abort. В релиз-версии не вычисляет аргумент (повод для ошибок, когда важная функциональность случайно внесена внутрь assert-выражения).
Нарушения допущений (assertion failure) могут быть разными
— по степени воздействия (от аварийного завершения до возобновляемого отказа в обслуживании)
— по трактовке (нарушение предусловий со стороны клиентского кода, внутренние ошибки)
— по способности к отладке (нужно ли останавливать отладчик, или можно просто отказать в обслуживании; нужно ли вести журнал и т.д.)
— по механизмам реализации (вызов аварийной функции, бросок исключения, возвращение кода ошибки и т.д.; как выводить сообщения в журнал и на экран; интеграция с отладчиком)
По-хорошему, получается, что нужно написать библиотеку поддержки assert'ов на разные случаи жизни.
Перекуём баги на фичи!
Re[3]: Почему редко используются assert'ы?
От:
Аноним
Дата:
10.10.05 17:54
Оценка:
Здравствуйте, Кодт, Вы писали:
А>>>Почему в большинстве библиотек которые я видел очень редко используются assert'ы? А>>>Типа "настоящие" программисты ошибок не совершают...
К>>Потому что assert — это самое убогое средство отладки, какое можно придумать. К>>В дебаг-версии он выводит assertion failed и убивает задачу по abort. В релиз-версии не вычисляет аргумент (повод для ошибок, когда важная функциональность случайно внесена внутрь assert-выражения).
К>Нарушения допущений (assertion failure) могут быть разными К>- по степени воздействия (от аварийного завершения до возобновляемого отказа в обслуживании) К>- по трактовке (нарушение предусловий со стороны клиентского кода, внутренние ошибки) К>- по способности к отладке (нужно ли останавливать отладчик, или можно просто отказать в обслуживании; нужно ли вести журнал и т.д.) К>- по механизмам реализации (вызов аварийной функции, бросок исключения, возвращение кода ошибки и т.д.; как выводить сообщения в журнал и на экран; интеграция с отладчиком)
К>По-хорошему, получается, что нужно написать библиотеку поддержки assert'ов на разные случаи жизни.
Что используете Вы, вместо ассертов?
Как ловите баги СВОЕГО кода?
Здравствуйте, Аноним, Вы писали:
К>>По-хорошему, получается, что нужно написать библиотеку поддержки assert'ов на разные случаи жизни.
А>Что используете Вы, вместо ассертов? А>Как ловите баги СВОЕГО кода?
Написана библиотека умных ассертов — отчасти, по мотивам советов Герба Саттера. (См. какую-то из его Exceptional книжек).
Есть ряд макросов, словесно описывающих суть ассерта — PRECONDITION, POSTCONDITION, ASSERTION общего назначения.
Они проверяют условия и в случае облома
— проходят через контрольную точку (где можно остановиться для отладки)
— кидают специальное исключение, которое
— — при размотке стека пишет в журнал, в каких функциях произошёл отстрел
— — ловится в самой нижней функции потока, откуда принимается решение остановить работу, перезапустить и т.д.
Есть здоровенное семейство макросов, которые прозрачно проверяют возвращаемые из функций коды ошибок (HRESULT'ы).
Оттуда можно или кинуться исключением подкласса logic_error (типа, штатная неприятность), или объявить assertion fault как выше.
Опять же, это средство самодокументирования кода.
Сейчас библиотека, во-первых, ещё довольно сырая (много велосипедных решений), а во-вторых, не public domain. Так что, увы, поделиться кодом не могу
Здравствуйте, Аноним, Вы писали:
А>Почему в большинстве библиотек которые я видел очень редко используются assert'ы? А>Типа "настоящие" программисты ошибок не совершают...
С. Макконнелл ("Совершенный код") говорит насчет ассертов так: "Используйте процедуры обработки ошибок для ожидаемых событий и
утверждения для событий, которые происходить не должны"
То есть, если входные данные поступили в функцию из ненадежного источника (пользователя), надо использовать процедуру обработки ошибок, иначе можно обойтись утверждением.
Например, для проверки предусловий в открытых членах класса следует использовать обработку ошибок (исключения или коды возврата), для внутренних (приватных, защищенных) – утверждения (assert)
Здравствуйте, Кодт, Вы писали: К>Есть ряд макросов, словесно описывающих суть ассерта — PRECONDITION, POSTCONDITION, ASSERTION общего назначения. К>Они проверяют условия и в случае облома К>- проходят через контрольную точку (где можно остановиться для отладки) К>- кидают специальное исключение, которое К>- — при размотке стека пишет в журнал, в каких функциях произошёл отстрел К>- — ловится в самой нижней функции потока, откуда принимается решение остановить работу, перезапустить и т.д.
У меня что-то похожее, только логика настраеваемая через установку хендлеров. Тобишь если в конкретном приложении нужна специальная логика (например стек можно более продвинуто оттрейсить) можно установить свой обработчик. Только вот исключения я в ассертах не кидаю.
Ну и ассёрты называются REQUIRE, VIOLATION, EMERGANCY. Плюс REQUIRE и VIOLATION бывают трёх видов. Обычные — из кода впринципе не удаляюся. DBG_* — проверяются только для отладочной версии, и STRICT_* — обычно включены для тестов независимо от того дебажная сборка или нет. Ещё есть WARNING, тоже бывает полезным, но очень редко. WARNING — это как вроде бы и не должно так быть, но с другой стороны и к сбою это не приведёт.
Смысл неудаляемых ассёртов в отлове фатальных сбоев невыявленных в процессе тестирования или последствий ошибок второго рода. В релизе помогает сгенерить крашдамп позволяющий составить представление о ситуации в которой произошл сбой.
И надо заметить, до того момента пока я не уверен что REQUIRE или VIOLATION тормозит код, переводить его в удаляемый STRICT_* я не буду. Себе дороже выйдет. DBG_* версии используются именно для отладки проблемных участков ибо отрабатывают только в отладочном коде.
Ещё очень важный момент, который как-то скипают люди пренебрегающие ассертами. Ассерты предназначены для отлова ошибок кодирования. Ситуаций которых в коде быть вообще не может и если такая ситуация произошла, то это уже всё, финиш. И если мы говорим о библиотеках то в них это необязательно ошибка алгоритма, но так же и нарушение контракта. Просто вопрос в жёсткасти реакции. Но моё имхо, что если некое поведение не предусмотренно программой, то дальнейшее её исполнение должно быть прервано. Уходм в отладчик или посылкой багрепорта, не суть важно. Важно что дальнейшее поведение программы непредсказуемо. Для отлова таких ситуаций и служат ассёрты.
Re[5]: compile time assert'ы
От:
Аноним
Дата:
11.10.05 09:23
Оценка:
Здравствуйте, _Winnie, Вы писали:
_W>И вообще, лишний код просто ради формирования сообщения об ошибке, что не работает на разных компиляторах — нафиг это надо? Чем проще, тем лучше.
Ха-ха-ха. Кто бы говорил.
Re[2]: А зачем выбрасывать asserts в release-версии?
Здравствуйте, MaximE, Вы писали:
ME>Часто это обеспечить очень сложно, поэтому может легко случиться так, что реальные входные данные вызвали последовательность действий, которые бы были вызвали ассерт, но это уже релизная версия и ассерта там, естественно, нет. Поэтому гораздо безопаснее не полагаться целиком на тестирование и ассерты, а всегда проверять условия в критичных местах.
А почему бы не оставлять assert'ы и в release-версии?
В конце концов assert выполняет несколько функций
1) Это хороший комментарий мыслей программиста о состоянии программы в данной точке кода. Особенно хорошо, что он формальный и сам проверяет свою актуальность
2) Он позволяет довольно задёшево поймать очень много ошибок.
3) Он повышает доверие к результату работы программы. Типа если программа отработала и все assert'ы успешно проверили свои условия, то результат более надёжен.
4) Это хороший инструмент для поддержки. Обычно, если в программе много assert'ов, то в случае обнаружение ошибки конкретный assert позволяет детектировать конкретную проблему. То есть после того, как один пользователь нашёл такие грабли и ты с ними разобрался, то ты можешь следующему пользователю, у которого такой же assert проваливается, выдать уже готовые рекоммендации. ТОже довольно удобно.
Так что не совсем понятно от чего бы не оставлять assert'ы всегда. Другое дело что может в них лучше делать не авост, а таки бросаться спецальным assert'овским исключением.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Почему редко используются assert'ы?
От:
Аноним
Дата:
11.10.05 16:38
Оценка:
Здравствуйте, Кодт, Вы писали:
Судя по вашим топикам на rsdn, Вы уже давно занимаетесь разработкой программ. Почему
>> Сейчас библиотека, во-первых, ещё довольно сырая (много велосипедных решений)
Ваш опыт разработок так и не привел к завершенной библиотеке?
Странная вещь получается, есть много разработчиков годы пишущих на Си, так и не решивших для себя каким образом использовать ассерты. Многие опытные разработчики вообще их не используют: сужу по куче библиотек в инете без единой строчки assert(...). Почему?
<...>
АК>То есть, если входные данные поступили в функцию из ненадежного источника (пользователя), надо использовать процедуру обработки ошибок, иначе можно обойтись утверждением.
АК>Например, для проверки предусловий в открытых членах класса следует использовать обработку ошибок (исключения или коды возврата)
Нарушения предусловия — баг программы (на то оно и предусловие, а не "ожидаемое событие"). Что толку от обработки ошибок в этом случае?
Корректность входных данных не может являться предусловием для функции, обрабатывающей данные из ненаднжного источника.
АК>для внутренних (приватных, защищенных) – утверждения (assert)
Только вот эти внутренние методы вызываются обычно из открытого интерфейса. Такое разделение IMHO нелогично.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Кодт, Вы писали:
А>Судя по вашим топикам на rsdn, Вы уже давно занимаетесь разработкой программ. Почему
>>> Сейчас библиотека, во-первых, ещё довольно сырая (много велосипедных решений)
А>Ваш опыт разработок так и не привел к завершенной библиотеке?
А>Странная вещь получается, есть много разработчиков годы пишущих на Си, так и не решивших для себя каким образом использовать ассерты. Многие опытные разработчики вообще их не используют: сужу по куче библиотек в инете без единой строчки assert(...). Почему?
А>Наверное, это риторический вопрос...
дык одно дело библиотека, другое приложение. Вот пример библиотечной ф-ии
double sqrt( double X ) {
assert( X >= 0.0 );
// тра-тата
}
или
double sqrt( double X ) {
if( X < 0.0 ) throw ....
// тра-тата
}
что лучше для библиотеки?
---
С уважением,
Сергей Мухин
Re[7]: Почему редко используются assert'ы?
От:
Аноним
Дата:
11.10.05 18:06
Оценка:
Здравствуйте, Сергей Мухин, Вы писали:
СМ>Здравствуйте, Аноним, Вы писали:
А>>Здравствуйте, Кодт, Вы писали:
А>>Судя по вашим топикам на rsdn, Вы уже давно занимаетесь разработкой программ. Почему
>>>> Сейчас библиотека, во-первых, ещё довольно сырая (много велосипедных решений)
А>>Ваш опыт разработок так и не привел к завершенной библиотеке?
А>>Странная вещь получается, есть много разработчиков годы пишущих на Си, так и не решивших для себя каким образом использовать ассерты. Многие опытные разработчики вообще их не используют: сужу по куче библиотек в инете без единой строчки assert(...). Почему?
А>>Наверное, это риторический вопрос...
СМ>дык одно дело библиотека, другое приложение. Вот пример библиотечной ф-ии
СМ>
СМ>double sqrt( double X ) {
СМ>assert( X >= 0.0 );
СМ>// тра-тата
СМ>}
СМ>
СМ>или
СМ>
СМ>double sqrt( double X ) {
СМ>if( X < 0.0 ) throw ....
СМ>// тра-тата
СМ>}
СМ>
СМ>что лучше для библиотеки?
Т.е. по вашему как раз библиотекам ассерты и не нужны? А нужны они только приложениям
Считаю что для проверки входных данных функции лучше использовать assert.
Проверять валидность данных нужно до вызова функции с этими данными.
double sqrt( double X )
{
assert( X >= 0.0 );
тра-тата
}
if (X < 0) throw...;
if (Y == 0) throw...;
...
sqrt(X);
log(Y);
Assert нужен разработчику кода, использующему данную библиотеку, на тот случай если он где-то забыл проверить данные поступившие от пользователя.
Здравствуйте, Аноним, Вы писали:
А>Т.е. по вашему как раз библиотекам ассерты и не нужны? А нужны они только приложениям А>Считаю что для проверки входных данных функции лучше использовать assert. А>Проверять валидность данных нужно до вызова функции с этими данными.
А>Assert нужен разработчику кода, использующему данную библиотеку, на тот случай если он где-то забыл проверить данные поступившие от пользователя.
данные поступившие от пользоватля, надо проверять логикой (т.е. if), а ассерты то есть, то нет (в release).
аналогично в библиотеке, вам дают ф-ию, и говорят, "вот ее поведение, оно отлично от при разных режимах компиляции" и такое терпеть?
Здравствуйте, Сергей Мухин, Вы писали:
СМ>дык одно дело библиотека, другое приложение. Вот пример библиотечной ф-ии
СМ>double sqrt( double X ) {
СМ>assert( X >= 0.0 );
СМ>// тра-тата
СМ>}
СМ>или
СМ>double sqrt( double X ) {
СМ>if( X < 0.0 ) throw ....
СМ>// тра-тата
СМ>}
СМ>что лучше для библиотеки?
Дело вкуса. Если я пишу в документации “X shall be a non-negative number”, то имею право при нарушении условия (а) бросать исключение, (б) выдавать неправильный ответ, (в) уходить в бесконечный цикл, (г) форматировать винчестер. Если я пишу в документации “if X is negative, throws std::domain_error”, то должен сделать проверку и бросить исключение.
С другой стороны — в C++ принято не платить за то, что не нужно. Поэтому клиент ожидает, что библиотечная функция не будет тратить время на проверку условия, которое, как он знает, всегда истинно. По крайней мере в релизе с оптимизацией.
Здравствуйте, Сергей Мухин, Вы писали:
А>>Assert нужен разработчику кода, использующему данную библиотеку, на тот случай если он где-то забыл проверить данные поступившие от пользователя.
СМ>данные поступившие от пользоватля, надо проверять логикой (т.е. if), а ассерты то есть, то нет (в release). СМ>аналогично в библиотеке, вам дают ф-ию, и говорят, "вот ее поведение, оно отлично от при разных режимах компиляции" и такое терпеть?
В таких случаях говорят: "поведение при аргументах, нарушающих контракт (предусловие), не определено"; а как именно оно не определено (расстрел памяти или отладочное прерывание) — дело хозяйское.
Предпочтительно, конечно, отладочное прерывание.
Но если пользователь зарубится на это, и сделает тандем из специального обработчика прерывания (который будет, скажем, бросать исключение) и ловца, и пользоваться этим — то не пошёл бы такой пользователь куда подальше.
set_assertion_handler(throw_oops);
try
{
for(int n=0; ; ++n) // а пёс его знает, до скольки там надо считать
direct_access_to_nth_item(n);
}
catch(oops) // типа, конец цикла
{
}
Пример — STL-ные алгоритмы. Они не контролируют выход итераторов за границы, отдавая эту миссию пользователю.
Но могут помочь ему в отладке, если у него самого руки недостаточно прямые. (Так поступает STLport).
Здравствуйте, Аноним, Вы писали:
>>> Сейчас библиотека, во-первых, ещё довольно сырая (много велосипедных решений)
А>Ваш опыт разработок так и не привел к завершенной библиотеке?
Опыт разработок и опыт сопровождения проектов — это разные вещи
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, Андрей Коростелев, Вы писали:
A><...>
АК>>То есть, если входные данные поступили в функцию из ненадежного источника (пользователя), надо использовать процедуру обработки ошибок, иначе можно обойтись утверждением. АК>>Например, для проверки предусловий в открытых членах класса следует использовать обработку ошибок (исключения или коды возврата) A>Нарушения предусловия — баг программы (на то оно и предусловие, а не "ожидаемое событие"). Что толку от обработки ошибок в этом случае? A>Корректность входных данных не может являться предусловием для функции, обрабатывающей данные из ненаднжного источника. АК>>для внутренних (приватных, защищенных) – утверждения (assert) A>Только вот эти внутренние методы вызываются обычно из открытого интерфейса. Такое разделение IMHO нелогично.
Ну смотри
Классическое представление ошибки — это цепочка Fault->Error->Failure (поцарапали DVD->сбойные блоки->файловый сервис обломается при попытке чтения этих блоков)
В то же время одно и то же Error-событие может рассматириваться как Fault в зависимости от контекста (например, если облом файлового сервиса для нас не фатален, так как мы можем это обработать в нашей програме)
В нашем случае имеем два контекста: пользовательский и системный (или назовем его программистский)
При возникновении ошибки в пользовательском контексте, система должна обработать ее на пользовательском уровне (выкинуть message box что пользователь был вообще-то неправ, введя "Леха" на запрос даты рождения)
Программистские же ошибки обрабатываются на программистском уровне (вылет + корка чтоб было над чем подумать).
С точки зрения пользовательского контекста имеем Fault: пользователь неверно ввел строку.
Error: функция открытого интерфейса получила неверные данные.
Теперь смотрим с точки зрения программиста: Fault: функция открытого интерфейса передала внутренней неверные входные данные
Error: assertion внутренней функции.
Отсюда видно, что assertion служит для контроля ошибок программиста, иначе ошибки следует обрабатывать
Здравствуйте, Андрей Коростелев, Вы писали:
АК>Классическое представление ошибки — это цепочка Fault->Error->Failure (поцарапали DVD->сбойные блоки->файловый сервис обломается при попытке чтения этих блоков) АК>В то же время одно и то же Error-событие может рассматириваться как Fault в зависимости от контекста (например, если облом файлового сервиса для нас не фатален, так как мы можем это обработать в нашей програме)
АК>В нашем случае имеем два контекста: пользовательский и системный (или назовем его программистский) АК>При возникновении ошибки в пользовательском контексте, система должна обработать ее на пользовательском уровне (выкинуть message box что пользователь был вообще-то неправ, введя "Леха" на запрос даты рождения) АК>Программистские же ошибки обрабатываются на программистском уровне (вылет + корка чтоб было над чем подумать).
АК>С точки зрения пользовательского контекста имеем Fault: пользователь неверно ввел строку. АК>Error: функция открытого интерфейса получила неверные данные. АК>Теперь смотрим с точки зрения программиста: Fault: функция открытого интерфейса передала внутренней неверные входные данные АК>Error: assertion внутренней функции.
АК>Отсюда видно, что assertion служит для контроля ошибок программиста, иначе ошибки следует обрабатывать
Ещё один момент: насколько вообще нужна отказоустойчивость?
Если ошибка программиста привела к сбою в недрах библиотеки — то, в конце концов, какая разница: дать ему по пальцам там или попытаться восстановиться (вернув код ошибки или исключение — которые в клиентском коде могут быть подавлены!) Фатальность предпочтительнее потому, что его легче обслужить, во-первых, и сложнее проигнорировать, во-вторых.
Если же ошибка в клиентском коде навела сбой в серверном приложении, то гораздо предпочтительнее восстановиться — пусть даже ценой замалчивания.
Здравствуйте, Аноним, Вы писали:
А>Почему в большинстве библиотек которые я видел очень редко используются assert'ы? А>Типа "настоящие" программисты ошибок не совершают...
Ну стандартный assert из runtime действительно весьма убог. Тут я с Кодт'ом вполне согласен.
Но сейчас есть много библиотек в которых разработаны более продвинутые версии макроса assert.
И, насколько я знаю, ими вполне пользуются.
Правда в развитых версиях assert'а есть много отличий, от стандартного.
1) Обычно есть несколько разных assert, которые убираются или не убираются в зависимости от того debug версия или нет
2) Обычно есь возможность управлять что будет, в случае провала условния в assert. При этом отдельно можно управлять что будет, если провалится условие, при инициализации статического объекта, а отдельно, если из кода, вызванного из main
3) Дефолтное поведение разное для разных контекстов. Например под Windows, для окошечного приложения -- MessageBox и исключение, для консольного -- вывод в консоль и исключение, а для сервиса запись в лог и исключение. Ну а в случае провала условия в конструкторе статического объекта -- исключение не кидается, потому что негде ловить.
Ну такое что-нибудь например. Возможны и более проработанные варианты. Например, обработчик может сразу формировать запрос в техподдержку и открывать в браузере страничку на сайте производителя софта, где возмможно уже описано что делать, в случае появления именно такого assert
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Почему редко используются assert'ы?
От:
Аноним
Дата:
13.10.05 18:27
Оценка:
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Аноним, Вы писали:
А>>Почему в большинстве библиотек которые я видел очень редко используются assert'ы? А>>Типа "настоящие" программисты ошибок не совершают...
E>Ну стандартный assert из runtime действительно весьма убог. Тут я с Кодт'ом вполне согласен. E>Но сейчас есть много библиотек в которых разработаны более продвинутые версии макроса assert. E>И, насколько я знаю, ими вполне пользуются.
E>Правда в развитых версиях assert'а есть много отличий, от стандартного.
E>1) Обычно есть несколько разных assert, которые убираются или не убираются в зависимости от того debug версия или нет E>2) Обычно есь возможность управлять что будет, в случае провала условния в assert. При этом отдельно можно управлять что будет, если провалится условие, при инициализации статического объекта, а отдельно, если из кода, вызванного из main E>3) Дефолтное поведение разное для разных контекстов. Например под Windows, для окошечного приложения -- MessageBox и исключение, для консольного -- вывод в консоль и исключение, а для сервиса запись в лог и исключение. Ну а в случае провала условия в конструкторе статического объекта -- исключение не кидается, потому что негде ловить.
E>Ну такое что-нибудь например. Возможны и более проработанные варианты. Например, обработчик может сразу формировать запрос в техподдержку и открывать в браузере страничку на сайте производителя софта, где возмможно уже описано что делать, в случае появления именно такого assert
Вопрос был не о конкретно стандартном макросе assert, а об ассерте как концепции отлова багов
Здравствуйте, Аноним, Вы писали: А>Вопрос был не о конкретно стандартном макросе assert, а об ассерте как концепции отлова багов
Наверное в публичные библиотеки не хотят тащить за собой нестандартный assert.
Но, тем не менее, например, приватные библиотеки, которые существуют в той конторе, где работаю (довольно большой) пользуются стандортизованной в конторе библиотекой assert'ов, и, соответсвенно ставят assert'ы везде где это надо.
Ну а в приложениях вообще постоянно ставят assert'ы, так что я думаю, дело в том, что стандартный assert убог, а остальные не стандартизованы.
Если, когда, в STL добавят нормальный assert его станут использовать и в библиотеках
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском