C++ для начинающих. Что такое assert

Мы люди, а все люди так или иначе однажды делают ошибки. Кто-то больше, кто-то меньше. В мире программирования ломать программу своими дерзкими действиями может как пользователь, использующий её, норовя запихать в данные — сущности неподходящих типов: то в целое число строку запихнёт, то возьмёт и что-нибудь на ноль поделит, — так и сами программисты своими неправильными вычислениями. Вот чтобы программист мог немного обезопасить себя от собственной погрешности, существует специальный макрос assert, действие которого такое же, как у if, но синтаксис немного другой.

  • Чтобы использовать assert, необходимо подключить заголовочный файл cassert или assert.h, если компилятор древний.


Сам по себе листинг #1 не имеет смысла, с его помощью только отображено, как в коде пишется assert. Обозначает показанное действе, что после того, как a задана, программист обещает компилятору, что a всегда равно 10. Чтобы понять пользу от assert, нужно посмотреть на ситуацию, когда программист мог совершить ошибку в своих расчётах. Это важно: программист в своих расчётах может совершить ошибку, и чтобы немного обезопасить себя вставляет в такие места, где могут быть просчёты, макрос assert. Возьмём один не практический, а академический пример, очень простой для демонстрации этого.

Новичок пишет код, в котором генерирует числа для заполнения ими массива. Ему не нужно генерировать ноль, потому что дальше на эти числа будут делиться другое число или другие числа. Этот новичок плохо понимает диапазоны и часто ошибается на единичку (довольно распространённая ошибка у новичков выходить за пределы массива на одно значение, например). Он откуда-то знает про assert и активно его использует. Пишет вот такой код:


Эта программа может несколько раз сработать как ни в чём не бывало, но поскольку в расчёте нашего новичка допущена ошибка, то или сразу, или, в худшем случае, спустя несколько запусков assert скажет своё резкое слово. В зависимости от среды разработки, языка, подключенных библиотек и определений тело assert может работать по-разному. Например, в приложении PC на языке C++ может вызываться исключение (throw/raise excepton), на языке C может происходить останов выполнения программы с выходом (abort, exit). Во встраиваемых приложениях (микроконтроллеры) обычной практикой реализации assert может быть простое бесконечное зацикливание, иногда это реализуется с выводом подходящего сообщения в последовательный порт (отладочную консоль, UART). Поскольку эту тему читают новички, то у вас скорее всего программа просто упадёт с характерным сообщением: {Assertion failed: выражение, имя_файла, номер_строки_файла}. Если программа всё-таки успеет упасть до того, как наш новичок пойдёт хвастаться своим друзьям готовым рабочим файлом, то наш новичок скорее всего сможет оперативно определить, где находится источник ошибки и исправить собственный просчёт.
Поскольку эта часть расчётов входит в обязанности нашего новичка-программиста, то целесообразно для проверок использовать не if, а assert. Если бы использовался if, то код бы засорился избыточной проверкой. А assert можно отключить при окончательной сборке программы с помощью отключающего этот макрос макроса NDEBUG.

  • Макрос NDEBUG надо прописывать до места прописанного включения assert
  • assert — это избыточные проверки, которые все внутри одной программы можно отключить одним движением: использованием макроса NDEBUG. Это намного удобнее, чем выкидывать избыточные if.

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

В листинге #4.1 произошёл выход за пределы массива и на экран пошли мусорные значения. Мы ушли далеко за разрешённую границу. Иногда так бывает, что мусорные значения очень похожи на настоящие и участвуют в вычислениях, понять, что они мусорные, сходу не получается. Коли расчёты входят непосредственно в сферу действий программиста, написавшего этот код, то проверки с помощью if избыточны. Но проверки с помощью assert хоть и избыточны, но могут быть отключены одним движением руки. Кроме того, у нас есть условие, которое точно должно выполняться, в совокупности с расчётами программиста этот фактор также способствует использованию assert для обозначения соглашения между компилятором и программистом о том, что программист суёт именно такие данные, какие показывает, а программа не работает с данными незадекларированными этим соглашением. Своеобразный симбиоз получается. Программист, чтобы обезопасить себя, делает соглашение:

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

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

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



Если в листинге #4 направить указатель c на любой адрес с нулевого, то ошибка будет исправлена. А в таком виде нарушается данное внутри вызываемой функции обещание, и assert сразу даёт об этом знать.
Конечно, assert помогает вылавливать ошибку в расчётах, но надо учитывать и то обстоятельство, что ошибка может затаиться и выскочить очень не скоро. Для демонстрации использую очень простой для понимания этого момента пример.



Сколько запусков пройдёт, пока ошибка проявится, не знает никто. Хоть этот пример и притянут за уши, он предназначен только для демонстрации самой возможности подобного развития событий.
Конечно же это не все возможные ситуации использования assert. Существует много других случаев. Но всё сводится к тому, что программист обещает компилятору, что в даваемых им компилятору расчётах будут определённые данные или наоборот — не будет их.
  • assert — это не средство борьбы с ошибками, а средство понижения сложности отладки и увеличения самотестируемости кода.
  • assert используют программисты для контроля самих себя.
  • assert — это соглашение между программистом и компилятором, что какое-то условие всегда выполняется.
  • С помощью assert происходит фильтрация и разделение проверок, нужных в ходе работы программы, и нужных самому программисту.
  • assert отключают, когда считается, что программа готова.
  • assert не исключает неудачу в ловле "скользкой" ошибки.
Иногда информации, даваемой assert по умолчанию, недостаточно для оперативного реагирования на исправление просчёта. В этом случае может помочь дополнительное информирование в случае срабатывания.



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

Добавить комментарий

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

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/mayk-makgrat/java-programmirovanie-dlya-nachinauschih-17254044/?lfrom=15589587
Яндекс.Метрика