
Качественный код – залог быстрой и успешной отладки
«Вы увидите несколько предупреждений при компиляции кода, но можете спокойно их игнорировать», - уверенно заявил разработчик. Накануне он связался со мной и заявил, что обнаружил ошибку в нашем компиляторе Embedded Workbench. После этого он просто прислал мне не только ту часть кода, в которой, возможно, скрыта проблема, а весь свой проект полностью.
Когда я компилировал проект на своем компьютере, «несколько предупреждений» на деле оказались 402-мя. Просканировав их, я нашел источник проблемы. Предупреждающее сообщение для ключевой строки кода гласило: «Неопределенное поведение: порядок доступа к volatile-переменным не гарантирован». Предупреждающее сообщение пыталось донести до нас, что метод, которым программист писал код, привел его в «темный угол» спецификации языка С, где все не так однозначно. Таким образом, когда при вызове функции аргументы представляют собой обращение к volatile-переменным или сами являются вызовами функций, компилятор сам может выбрать порядок выполнения действий.
Чтобы сделать код максимально производительным, компилятор может не просто идти слева направо - если это может сделать код более компактным и/или быстрым, можно изменить порядок обращения к функциям в зависимости от вида оптимизации, выбранной пользователем.
Правильная работа вышеупомянутого кода зависела от порядка выполнения действий – строго слева направо. Я показал разработчику, как переписать код так, чтобы все компиляторы интерпретировали его одинаково и, что более важно, чтобы он функционировал так, как и предполагалось изначально.
Остерегайтесь предупреждений
Данная ситуация подтвердила принцип, который я всегда использовал в профессиональной карьере: предупреждения - это ошибки, которые просто ждут подходящего момента, чтобы выйти на свет. Именно поэтому вы всегда должны пытаться избавиться от всех предупреждений и сделать код чистым, понимая, почему компилятор выдал предупреждение, и что оно действительно пытается вам сказать. Помня об этом, вы будете гораздо более готовы к тому, чтобы внести изменения в проект и повысить надежность кода.
Компиляторы и компоновщики обычно не изучают ваш код достаточно подробно. Следовательно, для полной уверенности в отсутствии дефектов недостаточно просто не получить предупреждений при сборке. Скорее всего, это только первый шаг к упрощению отладки вашего приложения. Более тщательный анализ кода необходим для того чтобы не допустить распространенных ошибок, с которыми сталкиваются разработчики, а также чтобы избежать неопределенного поведения языка C и C++, о котором упоминалось ранее. Другими словами, вы должны серьезно относиться к качеству кода.
Разработчики обычно применяют два подхода для получения высокого уровня качества кода: пользуются стандартами оформления кода и инструментами статического анализа. Использование обоих методов в сочетании с тестированием не только сделает ваш код максимально чистым, но и сократит время выхода на рынок, поскольку ваше приложение гораздо быстрее примет вид, готовый к релизу.
Как известно, чем позже вы найдете ошибку, тем дороже будет ее исправить. Мы все слышали эту банальную фразу столько раз, что даже не подвергаем сомнению ее достоверность. Но вы когда-нибудь задумывались, почему она верна? Чтобы наглядно это представить, давайте взглянем на рис. 1, где изображена типичная кривая наработки на отказ (MTTF).
Рис. 1. Наработка на отказ
Как видите, когда вы впервые начинаете тестировать приложение, легко обнаружить довольно большое количество дефектов. Но с развитием программного продукта требуется все больше и больше времени для поиска следующей ошибки. Причина, по которой исправление ошибки на более позднем этапе цикла тестирования обходится дороже, заключается в том, что дефект искусственно искажает вашу кривую, заставляя тратить на тестирование еще больше времени, чтобы проект соответствовал критериям выпуска в вашей компании. Следовательно, чем раньше вы обнаружите его при тестировании, тем меньше он повлияет на график проекта и тем экономичнее будет его исправить.
А как насчет того, что вы можете сократить время тестирования, изначально сократив количество ошибок в вашем коде? Вот, где высокое качество кода сыграет вам добрую службу. Пользуясь стандартами оформления кода и статическим анализом, вы можете устранять ошибки еще при проверке кода. После проверки кода в сборке каждый дефект будет работать против вас, увеличивая время выхода продукта на рынок. Однако если вы обнаружите его раньше, это будет выглядеть так, будто ошибок вовсе не было.
Стандартные процедуры
Теперь, когда мы знаем, почему так важно иметь качественный код, давайте рассмотрим два известных способа достижения этой цели. Первый способ заключается в реализации стандартов оформления кода. Во многих организациях существуют стандарты, которые устанавливают такие критерии как имена переменных и функций, соглашения о вызовах, структура комментариев и так далее. Некоторые зашли настолько далеко, что ограничивают фактический размер каждого отдельного фрагмента. Идея состоит в том, чтобы улучшить читаемость кода, упростить поиск ошибок и повысить удобство сопровождения.
Все эти глобальные идеи должны составлять основу стандартов, но есть стандарты, которые выходят за рамки этого уровня. Одним из самых популярных является Motor Industry Software Reliability Association, или MISRA С (Ассоциация надежности программного обеспечения на С для автомобильной промышленности), которая, как следует из названия, зародилась в автомобильной сфере для продвижения безопасных и надежных методов встраиваемого кодирования. Эти же правила используются во многих других отраслях индустрии, таких как авионика, медицинские и промышленные средства управления и даже потребительские товары. Это связано с тем, что стандарт пытается удалить все неопределенные варианты поведения в C, а также не позволяет разработчикам применять рискованные методы программирования, подверженные ошибкам. Например, посмотрите на этот фрагмент кода:
if((a==b)&&(c--)){...}
Что здесь не так? Вы, вероятно, не видите здесь подвоха, но здесь есть проблема, которую MISRA C пытается избежать. Проблема с этим утверждением состоит в том, что уменьшение значения «c» происходит только тогда, когда «a» равно «b». Хотя может случиться так, что это именно то, чего вы хотели. Вы даже готовы поспорить, что любой, кто просматривает или использует ваш код, также понимает это. Но эту деталь легко пропустить и допустить ошибку, поэтому MISRA C не пропустит такой код - он может легко добавить багов в вашу корзину. Вот еще один пример:
a == 3 || b> 5
Что в этой строчке имеет приоритет? Трудно запомнить, поэтому MISRA C делает логические AND и OR приоритетными выражениями, заставляя заключать их в круглые скобки, например:
(a == 3) || (b > 5)
Это делает код более читабельным. Скобки не накладывают на вас каких-либо ограничений – используйте их свободно! В зависимости от того, какую модификацию MISRA C вы используете (1998, 2004 или 2012), существует до 226 различных правил, подобных этому, которые помогут вам избежать распространенных ошибок в коде и, следовательно, уменьшат количество ошибок, обнаруженных во время формального тестирования, что значительно сокращает объем работы при отладке, необходимой вашему приложению. Существует даже MISRA C++, чтобы помочь вам безопасно писать код на C++.
Статический анализ
Второй способ улучшить качество кода - провести статический анализ вашего приложения. Большинство инструментов статического анализа может проверять ваш код на наличие несоответствий MISRA C/C++, но лучшие инструменты выходят за рамки этого. Например, инструмент статического анализа C-STAT в IAR Embedded Workbench добавляет к базовому набору несколько сотен правил, которые взяты из других источников, таких как Common Weakness Enumeration (CWE) от компании Mitre.
CWE представляет собой список наиболее распространенных источников ошибок в коде приложения. Он был составлен в результате исчерпывающих исследований, проведенных сотрудниками Mitre. Например, они не рекомендуют делать сравнения чисел при помощи операторов «==» или «!=» либо использовать делитель без проверки на ненулевое значение. Более сложные инструменты статического анализа, такие как C-STAT, также могут находить пути в коде, где вы разыменовываете указатель NULL, и показывать вам путь через код, который делает этот указатель пустым, когда происходит разыменование. Они могут сделать то же самое и для неинициализированных переменных.
Кроме того, CWE защищает от неправильного использования указателя, напрямую обращаясь к члену структуры, используя смещение структуры. Если вы измените структуру, это может легко вызвать кучу ошибок в вашем коде, и все это задержит выпуск вашего проекта.
Заключение
Мы рассмотрели лишь малую часть дефектов кода, которые вы можете найти с помощью статического анализа. Устранение этих проблем улучшает качество кода и, следовательно, уменьшает объем трудозатрат, необходимых для отладки.
А что же случилось с разработчиком, которого я упомянул в начале статьи? Я рассказал ему о своих принципах восприятия предупреждений как ошибок и посоветовал ему потратить некоторое время на их очистку, чтобы улучшить качество кода. Он сказал, что сделает это и поблагодарил меня за помощь. Примерно через неделю он столкнулся с другой проблемой и попросил о помощи. Он сказал, что отправит мне новый дамп кода и очень удивил меня, заявив, что он убрал все предупреждения! Я был крайне приятно удивлен, пока не получил новый дамп и не понял, что он просто решил подавить предупреждения при диагностике во время сборки. Некоторые люди ничему не учатся, но мне приятно думать, что всех нас можно научить каким-либо новым трюкам.
Если вы хотите тратить меньше времени в конце отладки проекта, потратьте немного больше времени в его начале, используя стандарты оформления кода и статический анализ. Это поможет вам улучшить качество исходного кода. Как заметил Бенджамин Франклин, «унция профилактики стоит фунта лечения».
Источник: https://www.electronicdesign.com
Опубликовано: 18.10.2019