Легко попробовать статический анализатор кода. Но требует навыков, чтобы внедрить его в разработку старого крупного проекта. При неправильном подходе анализатор может добавить работы, замедлить разработку и демотивировать команду. Кратко обсудим, как правильно интегрировать статический анализ в процесс разработки и начать использовать его как часть CI/CD.

Введение

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

Наша команда PVS-Studio предлагает свой взгляд на эту тему. Давайте посмотрим, как вообще возникает проблема встраивания статического анализатора кода, как эту проблему преодолеть и как поэтапно безболезненно ликвидировать технический долг.

проблематика

Обычно несложно запустить анализатор и посмотреть, как он работает [1]. Вы можете увидеть некоторые интригующие ошибки или даже пугающие потенциальные уязвимости в коде. Можно даже что-то исправить, но после этого многие программисты опускают руки.

Все статические анализаторы выдают ложные срабатывания. Это особенность методологии статического анализа кода, и с этим ничего не поделаешь. Так что это неразрешимая проблема, что подтверждается теоремой Райса [2]. Алгоритмы машинного обучения тоже не помогут [3]. Если даже человек не всегда может сказать, неверный ли тот или иной код, от программы этого ждать не стоит :).

Ложные срабатывания не проблема, если статический анализатор уже настроен:

  • Нерелевантные наборы правил отключены;
  • Отдельные неактуальные диагностики отключены;
  • Если мы говорим о C или C++, макросы, содержащие специфические конструкции (из-за которых бесполезные объекты появляются в любом месте, где используются такие макросы), размечаются;
  • Пользовательские функции, выполняющие действия, аналогичные системным, размечены (пользовательский аналог memcpy или printf) [4];
  • Ложные срабатывания выборочно отключаются с помощью комментариев;
  • И так далее.

В этом случае можно ожидать низкий уровень ложных срабатываний порядка 10–15% [5]. Другими словами, 9 из 10 предупреждений анализатора будут указывать на реальную проблему в коде или как минимум на код с сильным запахом. Согласитесь, такой сценарий крайне приятен, а анализатор становится настоящим другом программиста.

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

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

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

Программисты все смотрят, смотрят на все эти предупреждения на старом рабочем коде… И думают: давайте без статического анализа. Давайте напишем новую полезную функцию.

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

Это та же аналогия, что и с предупреждениями компилятора. Не случайно рекомендуется иметь 0 предупреждений компилятора. Если будет 1000 предупреждений, то когда их станет 1001, никто не обратит на это внимание, и непонятно, где искать это самое новое предупреждение.

Хуже всего в этой истории, если кто-то сверху заставит вас использовать статический анализ кода в этот момент. Это только демотивирует команду, так как с их точки зрения будет дополнительная бюрократическая сложность, которая только мешает их работе. Отчеты анализатора никто не будет просматривать, а все использование будет только «на бумаге». То есть формально анализ встроен в процесс DevOps, но на практике это никому не выгодно. Мы слышали подробные истории от посетителей конференции, когда болтали у стендов. Такой опыт может надолго, если не всегда, отбить у программистов охоту пользоваться инструментами статического анализа.

Внедрение анализатора и устранение технического долга

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

CI/CD

Более того, анализатор можно сразу сделать частью непрерывного процесса разработки. Например, в дистрибутиве PVS-Studio есть утилиты для удобного просмотра отчета в нужном вам формате и оповещения разработчиков, написавших проблемные участки кода. Тем, кого больше интересует запуск PVS-Studio из систем CI/CD, рекомендую ознакомиться с соответствующим разделом документации и циклом статей:

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

Замораживание существующего технического долга и работа с новыми предупреждениями

Современные коммерческие статические анализаторы позволяют просматривать только новые предупреждения, которые появляются в новом или модифицированном коде. Реализация этого механизма отличается, но суть одна. В статическом анализаторе PVS-Studio этот функционал реализован следующим образом.

Для быстрого перехода к статическому анализу мы предлагаем пользователям PVS-Studio применить механизм подавления массовых предупреждений [6]. Общая идея следующая. Представьте, пользователь запустил анализатор и получил много предупреждений. Поскольку проект, который разрабатывается много лет, жив, до сих пор развивается и приносит деньги, то, скорее всего, в отчете не будет много предупреждений, указывающих на критические дефекты. Другими словами, критические ошибки уже исправлены за счет более дорогих способов или с помощью отзывов клиентов. Таким образом, все, что сейчас находит анализатор, можно считать техническим долгом, сразу пытаться устранить который нецелесообразно.

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

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

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

Исправление ошибок и рефакторинг

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

if (a = b)

Большинство компиляторов и анализаторов C++ жалуются на такой код, так как велика вероятность того, что они действительно хотели написать (a == b). Но есть негласное соглашение, и это обычно отмечается в документации, что если есть дополнительные скобки, то считается, что программист написал такой код намеренно, и для него не нужно вызывать предупреждение. Например, в документации PVS-Studio к диагностике V559 (CWE-481) четко указано, что следующая строка будет считаться правильной и безопасной:

if ((a = b))

Другой пример: break забыт в этом коде C++ или нет?

case A:
  foo();
case B:
  bar();
  break;

Здесь анализатор PVS-Studio выдаст предупреждение V796 (CWE-484). Возможно, это не ошибка, и тогда следует подсказать анализатору, добавив атрибут [[fallthrough]] или, например, __attribute__((fallthrough)):

case A:
  foo();
  [[fallthrough]];
case B:
  bar();
  break;

Можно сказать, что такого рода изменения кода на самом деле не исправляют ошибки. Да, это так, но есть еще два полезных следствия. Во-первых, отчет анализатора избавляется от ложных срабатываний. Во-вторых, код становится более понятным для людей, которые занимаются его сопровождением. Что крайне существенно! Ради одного этого уже стоит пойти на небольшой рефакторинг, чтобы код стал понятнее и проще в обслуживании. Так как анализатор не понимает, нужен «брейк» или нет, то и коллегам-программистам будет непонятно.

Помимо исправления ошибок и рефакторинга, вы можете выборочно подавлять ложные предупреждения анализатора. Вы можете отключить некоторые неактуальные диагностики. Например, некоторые считают бессмысленными предупреждения V550 о сравнении значений float/double. А некоторые считают их значительными и достойными изучения [7]. Команда разработчиков должна решить, какие предупреждения актуальны, а какие нет.

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

Также мы всегда помогаем нашим клиентам настроить PVS-Studio, если возникают какие-то трудности. Более того, были случаи, когда мы сами устраняли ложные предупреждения и исправляли ошибки [8]. На всякий случай решил упомянуть, что возможен и такой вариант расширенного сотрудничества :).

Храповой метод

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

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

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

Более подробно эта методология описана в очень интересной статье Ивана Пономарева «Внедряйте статический анализ в процесс, а не просто ищите с его помощью баги», которую рекомендую прочитать всем, кто заинтересован в повышении качества кода.

Вывод

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

Есть и другие типичные сомнения относительно того, может ли статический анализатор действительно быть удобным и полезным. Большинство этих сомнений я попытался развеять в публикации Почему стоит выбрать статический анализатор PVS-Studio для интеграции в процесс разработки [9].

Спасибо за Ваше внимание! Заходите скачать и попробовать анализатор PVS-Studio.

Дополнительные ссылки