Comments 48
Тема действительно важная, но я бы посмотрел на другие аспекты:
Принципиально, чтобы программисту было максимально просто добавлять проверку инвариантов, не задумываясь, куда что логируется и выводится.
Очень часто приходим к тому, что простой остановки программы недостаточно, а нужно куда-то сохранить информацию об ассерте, то есть стандартный
assertне подходит, нужен свой макрос.Отключение ассертов в релизе тоже не очевидная необходимость. Например, в Chromium недавно стали уходить от debug-only проверок. Поверьте, производительность им тоже важна, но, видимо, баланс пользы сложился в сторону проверок.
в Chromium недавно стали уходить от debug-only проверок
можете указать, где почитать про это, интересны мотивы?
Быстрый поиск не нашел по этой теме ничего толкового.
Можно начать отсюда -https://groups.google.com/a/chromium.org/g/cxx/c/cy579lMzgTw/m/9YzIgUMkAAAJ
Очень часто приходим к тому, что простой остановки программы недостаточно, а нужно куда-то сохранить информацию об ассерте
Assert вызовет abort(), тот посылает SIGABRT, и по-умолчанию создаётся core dump.
Обычно вы запускаете программу/демон из какой-то обёртки на shell/python, и если программа завершила аварийно, то можно посмотреть создалась ли кора, и зайти gdb bt, и прислать, например, на email.
Ни разу не сталкивался с проектом, где мог бы работать стандартный assert. Очень часто ошибку надо не только поймать, но и о ней всем и правильно сообщить.
Лучшее применение ассертам - контракт. Но в плюсах пока нормальных контрактов не завезли, и если хотите их реализовывать - проще передавать уже валидированные (желательно в компилтайме) объекты, чем валидировать по месту приёма.
Далеко не везде приемлема ситуация, когда программе позволительно падать при ошибках программиста. Особенно странно это выглядит с тегом "системное программирование". Вы бы хотели, чтобы у вас, например, операционная система падала на ассертах?
И кстати не раз видел вываливающиеся ассерты в разном софте. Если честно подход не обрабатывать ошибку в релизе очень странный, и по-моему плохо применим в реальной жизни. Ну или точнее может быть применим где-то в шаблонном программировании, где у нас ассерт тестирует размеры переменных например - нечто что можно сделать в процессе компиляции, но не процессе работы.
при ошибках программиста
согласен с вами, то что не везде падение позволительно. Но для анализа того, насколько неверный код меняет логику программы, нужно слишком много ресурсов. Проще упасть и затем исправить ошибку.
Если программа находится перед программистом, то проще упасть и исправить. А если управляет каким-то HA процессом (для примера, подачей топлива в двигатель автомобиля), то совсем неправильно падать, а надо предусматривать код для парирования ошибок программиста.
Везде, конечно, своя специфика, но поддержу мнение предыдущего оратора @Goron_Dekar – я тоже не сталкивался с проектами, где был бы оправдан стандартный assert с падением (кроме совсем небольших программ).
но есть ли гарантия что программа верно подает топливо? Раз есть логическая ошибка в коде.
Даже если программа не совсем верно подаёт топливо, то это лучше, чем вообще заглушить двигатель на ходу. В таких случаях предусматривается несколько контуров обработки, резервирующих друг друга, и у вас, допустим, вырастет расход бензина, но автомобиль продолжит движение.
А что лучше, остановить двигатель на ходу, или, заведомо зная, что подача топлива работает неверно, продолжать упорно лить до гидроудара?
Продолжать лить, вывести предупреждение, что нужно срочно съехать на обочину и остановить машину, разумеется. Смысл таких обработок, что бы смягчить последствия и дать минимальную работоспособность для ручной обработки пользователем. Мгновенный abort практически всегда вызывает куда более катастрофические последствия, чем некорректная работа, в данном случае авария с потенциально летальным исходом
abort всегда можно вызвать и без assert, если нужно, но с ним как раз получится, что указанная ошибка в релизе обработана не будет вовсе
Как вариант, критически важная программа на assert-е может упасть, а внешняя по отношению к ней система запустить вместо неё аварийную замену (или просто перезапустить её. Или перезапустить несколько раз, а если не помогло - перезапустить аварийную замену).
Что безопаснее, при обнаружении внутренних ошибок - проглотить их и сделать вид, что всё ОК, когда на самом деле не ОК, или явно раскрутить цепочку обработки отказа?
В таких системах при возникновении критической ошибки производится принудительна перезагрузка либо, если програма зависла, перезагрузка по сработке сторожевого таймера (он реализован аппаратно). Время перезагрузки составляет милисекунды, поэтому вы даже не заметите сбоя в работе двигателя. Ну и конечно сохраняется код неисправности в память. Даже если система зависла не записав код неисправности, микропроцессор при загрузке будет знать по какой причине произошёл сброс и ПО сохранит соответствующий код неисправности.
Использование в релизе в таких системах ассертов вопрос дискуссионный. Многие разработчики встраеваемых систем ассерты оставляют. Сам порой поступаю также и видел их в прошивах эбу bosch.
Это эквивалент kernel panic на Linux или BSOD на венде.
А вы бы хотели, чтобы вместо этого система продолжила работать, но делала бы что-нибудь нввидимо и неправильно? Например, какая-нибудь важная ядерная нитка подвисла бы навечно и перестала бы отрабатывать относящиеся к ней запросы. И в результате, например, система делала бы вид, что работает, но записи на диск накапливались бы в памяти, а на диск бы не попадали.
Может всё же в безнадёжной ситуации лучше тогось, чем чтобы программа прикидывалась живой?
Смотря какая программа и в какой ситуации. Если вы откроете журнал ядра ОС, то увидите, что ошибки там происходят постоянно. А kernel panic - совсем уж редкий вариант.
Ошибки ошибкам рознь.
Если это какие-то неправильные данные, пришедшие снаружи, некорректное или неожиданное поведение аппаратуры, нехватка каких-то ресурсов, типа памяти и т.п., то для ядра это - нормальные, штатные ситуации, которые ядро должно корректно отрабатывать.
Но если ошибка именно во внутренней логике кода, как вы прикажете её отрабатывать?
BSOD выводит информацию, генерирует qr, паркует харды, сбрасывает кэши. Это уже не assert.
Может всё же в безнадёжной ситуации лучше тогось, чем чтобы программа прикидывалась живой?
Случаи бывают разные. Если имеем управление автономным аппаратом, то в любой ситуации ассерт - однозначно кранты, а без него может и вытянет. Даже если полагаться на перезапуск упавшего, то, что отвнчает за перезапуск, падать не должно.
Другой случай, когда ошибка - это не совсем всё, а только потеря части результата. Игнорирование ошибки может позволить таки получить оставшуюся часть.
Если программист пишет if, а не assert – скорей всего, у него есть на это причины. Например, сегодня переменная точно инициализируется в том же модуле, а завтра может и нет – в зависимости от какого-то неочевидного условия, и при проверке на дебаг-версии на это не наткнулись, а в релизе выстрелит.
Assert – пометки "для себя", на совсем уж очевидные случаи. If – уже более серьёзная проверка. Ну и третий случай – if, который уберёт компилятор (ибо увидит, что 5 строчками выше переменной присваивается значение).
Послушай птичка, что я тебе скажу. Щас за пять минут придрочимся к ассертам и полетим..
process(Config* config)
Если передавать как ссылку, то и проверять на Nullptr не надо ;-)
гораздо понятней будет
А потом падает, т.к. при сборке на прод забыли файл положить. Или создание конфига не удалось... и пофиг, что в том же модуле. if и assert невзаимозаменяемые в общем случае.
А какой смысл ей не падать, если нет необходимого файла?
Поискать файл в другом месте? Сообщить, что нет файла? Создать файл по-умолчанию?
Ассерты по большей части в тестах находятся. А в боевом коде их почти нет - там есть макросы, которые логируют ошибки, и в зависимости от макроса либо приводят к падению (exception имеется ввиду), либо нет. Exceptions либо обрабатываются выше, либо нет.
В боевом коде ассерты могут быть на время разработки какой либо фичи. Потом их убирают и заменяют тестами.
Очень странная статья. За предлагаемый подход я разворачиваю MRы.
Хочется ассерт - ставится ассерт, а после него лог и выход из функции.
К сожалению, аssert не спасет от разыменования nullptr в релизе. А в C++26 завезли контракты специально для этой цели.
Да, assert не спасает в релизе, он и не должен этого делать. Его задача отловить баг в рантайме на этапе разработки. Наврятли где-то вся разработка сразу в релизе ведется.
В реальном проекте покрыть всю логику и все бранчи тестами нереально. Правильная деградация - это тоже часть логики программы, тогда как ассерт - это верный путь к UB на проде, и может быть ещё хуже. Для горячих мест окей, но много ли их реально?
А так, конечно, разработка ведётся также и на проде (в релизе). Пользователи присылают баг-репорты, а анализ логов и корок даёт много полезной инфы, причём в реальных юзкейсах. Чем не тестирование!
Мы не тестируем на животных

Открою тайну. Иногда код вообще не запускается в дебаге, потому что у целевой платформы нет избытка мощности для выполнения дебажной сборки.
В такой ситуации дебаг и юнит-тесты можно использовать на хостовой машине со слоем аппаратной абстракции или в эмуляторе (qemu...)
assert имеет на самом деле весьма узкое применение, аргументация о падении программы в дебаге понятна, но в дебаге и не суть важно, если она продолжит работать некорректно, но с логами. А вот отсутствие проверок в рантайме может привести к катастрофическим последствиям. И я могу сходу представить огромную палитру багов от разработчика, которые очень сильно пострадают от assert. assert актуален только для стабильных compile-time ошибок, что большая редкость.
Допустим у нас какая-то data-driven разработка, мы читаем информацию из xml, собранного нами же самими. Делается assert на ошибку разработчика, мы множество раз протестировали в дебаге все работает. А потом в рантайме файл меняется, assert в релизе не срабатывает и у нас посыпалась вся логика без какой либо обработки. И все что бы сохранить парочку cpu инструкций!
Не чушь ли, товарищи. assert имеет место быть, но в узком направлении ошибок, статья гипер-обобщает, на мой взгляд
ПО моему опыту assert вещь почти бесполезная. На продовых данных они все равно не работают. Если проверка критична, она должна быть в релизе. А если некритична, то тогда она бесполезна.
Заменяем лишние if-проверки на assert для инвариантов кода в C/C++