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

Введение

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

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

Текст статьи может относиться к любому инструменту и не имеет ничего общего с самим PVS-Studio. Эту же статью может написать любой другой разработчик из GCC, Coverity или Cppcheck.

Обработка ложных срабатываний вручную

Прежде чем перейти к основной теме, я хотел бы уточнить пару моментов, касающихся разметки ложных срабатываний. У меня сложилось впечатление, что некоторые люди начали писать негативные комментарии, не изучив должным образом, о чем идет речь. Я видел такие комментарии:

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

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

  • Подавление ложных срабатываний на определенной строке с помощью комментариев.
  • Массовое подавление предупреждений, вызванных использованием макроса. Это также можно сделать с помощью специальных комментариев.
  • То же и для строк кода, содержащих определенную последовательность символов.
  • Полное отключение ненужных предупреждений с помощью настроек или специальных комментариев.
  • Исключение фрагмента кода из анализа с помощью #ifndef PVS_STUDIO.
  • Изменение настроек определенной диагностики с помощью специальных комментариев. Они описаны в определенных диагностических сообщениях (см. Пример V719: V719_COUNT_NAME).

Подробнее об этих возможностях можно узнать в разделе документации Подавление ложных срабатываний. Вы также можете отключить предупреждения или подавить предупреждения в макросах с помощью файлов конфигурации (см. Pvsconfig)

Отдельно стоит отметить систему подавления массовых ложных срабатываний с помощью специальной базы разметки. Это позволяет быстро интегрировать анализатор в процесс разработки крупных проектов. Идеология этого процесса описана в статье Лучшие практики PVS-Studio (теперь с поддержкой C #).

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

Теоретическая справка

Теперь немного теории. Каждое предупреждение анализатора имеет две характеристики:

  • Серьезность ошибки (насколько она фатальна для программы).
  • Уверенность в ошибке (вероятность того, что это настоящий дефект, а не просто код, который анализатор сочтет подозрительным)

Эти два критерия можно комбинировать в любой пропорции. Итак, мы можем описать виды диагностики с помощью двухмерного графа:

Рисунок 1. Диагностику можно оценить по серьезности и достоверности (надежности).

Я приведу несколько пояснительных примеров: Диагностика A, обнаруживающая, что файл * .cpp не имеет заголовков из комментариев, будет расположена в правом нижнем углу. Забытый комментарий не приведет к сбою программы, хотя с точки зрения стандарта кодирования команды это ошибка. Можно точно сказать, есть комментарий или нет. Поэтому показатель достоверности очень высок.

Диагностика B, обнаруживающая, что некоторые члены класса не инициализированы в конструкторе, будет расположена в середине верхней части. Вероятность этой ошибки не очень высока, поскольку анализатор может просто не понимать, как и где инициализируется этот член (это сложно). Программист может выполнить инициализацию после выполнения конструктора. Таким образом, неинициализированный член в конструкторе не обязательно является ошибкой. Но эта диагностика находится в верхней части графика, потому что если она действительно указывает на ошибку, это будет критично для программы. Использование неинициализированной переменной - серьезный недостаток.

Надеюсь, идея понятна. Однако, думаю, читатель согласится, что такое распределение ошибок на графике сложно для восприятия. Поэтому некоторые анализаторы упрощают этот график до таблицы из 9 или 4 ячеек.

Рисунок 2. Упрощенный вариант классификации. Используя 4 ячейки.

Именно так поступали авторы анализатора Goanna до того, как они были куплены компанией Coverity, которая позже была куплена Synopsis. Они классифицировали предупреждения, выдаваемые анализатором, относив их к одной из 9 ячеек.

Рис. 3. Фрагмент справочника Goanna (версия 3.3). Использует 9 ячеек.

Однако этот метод не очень распространен и неудобен в использовании. Программисты хотят, чтобы предупреждения располагались на одномерном графике: не важно, ›важно. Это более привычно, поскольку предупреждения компилятора основаны на тех же принципах.

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

Рисунок 4. Предупреждения высокой степени серьезности проецируем на строку. Ошибки начинают классифицироваться по степени достоверности.

Мы выявляем только те ошибки, которые могут привести к некорректной работе программы. Забывание комментария в начале файла может не привести к сбою программы и нас не интересует. Но мы ищем неинициализированные члены класса, потому что это критическая ошибка.

Таким образом, нам необходимо классифицировать ошибки по уровню их достоверности. Этот уровень достоверности распределяет предупреждения анализатора на три группы (высокий, средний, низкий).

Рисунок 5. Фрагмент интерфейсного окна PVS-Studio. Мы видим общую аналитическую диагностику высокого и среднего уровня.

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

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

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

Ложные срабатывания и уровни достоверности

Предупреждения PVS-Studio обнаруживают те фрагменты кода, которые с большей или меньшей вероятностью приводят к серьезным проблемам в работе программы. Поэтому уровни предупреждений в PVS-Studio - это не уровень серьезности, а их достоверность. Однако серьезность также может приниматься во внимание при распределении предупреждений по уровням; но мы не будем вдаваться в подробности, так как нас больше интересует общая картина.

Вкратце: Уровни указывают на достоверность сообщенной проблемы.

Критика, высказанная в предыдущей статье, была в основном против идеи потери полезных предупреждений при борьбе с ложными срабатываниями. На самом деле предупреждения не теряются - они просто относятся к разным уровням серьезности. А те редкие варианты ошибок, которые так волновали наших читателей, обычно просто доходят до уровня Low, который мы обычно не рекомендуем к просмотру. Полностью пропадают только бессмысленные предупреждения.

Рис. 6. Хорошо иметь что-нибудь на всякий случай. Но в какой-то момент вам следует остановиться.

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

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

char *p = (char *)malloc(strlen(src + 1));

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

Можно было предположить, что программист хотел написать этот код, но вероятность очень мала. Именно поэтому уровень достоверности этого предупреждения очень высок, и мы относим его к группе предупреждений высокого уровня.

Кстати, ложных срабатываний в этой диагностике нет. Если этот шаблон был найден, значит, произошла ошибка.

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

y = (A)(B)(A)(B)x;

Я даже не помню, зачем мы сделали эту диагностику. До сих пор я не видел, чтобы эта диагностика выявила настоящую ошибку. Обычно он находит повторяющийся код (особенно в сложных макросах), но не ошибки.

Большинство диагностик меняют уровни в зависимости от уверенности анализатора в том, что он обнаружил настоящую ошибку.

Мы интерпретируем уровни следующим образом:

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

if (A == B)
  A = 1; B = 2;

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

if (A == B)
  A = 1;
B = 2;

Средний (второй уровень). Этот код содержит ошибку, но анализатор не уверен. Если вы устранили все предупреждения высокого уровня, было бы полезно поработать с ошибками среднего уровня.

Низкий (третий уровень). Это предупреждения с низким или низким уровнем достоверности, и мы вообще не рекомендуем их просматривать. Обратите внимание, когда мы пишем статьи о проверках проектов, мы учитываем только High и Medium уровни, а о предупреждениях Low Level не пишем вообще.

Когда мы работали с проектом Unreal Engine, мы поступили так же. Нашей целью было устранить все предупреждения первого и второго уровня. Мы не учли предупреждения о низком уровне.

Как я уже сказал, большинство диагностик может иметь разные уровни в зависимости от набора характеристик. Некоторые характеристики могут увеличиваться, а другие снижать уровень уверенности. Они выбираются эмпирически на основе тестирования диагностики более чем на 100 проектах с открытым исходным кодом.

Посмотрите, как диагностика может перемещаться по разным уровням. Возьмем для примера диагностику V572. Эта диагностика предупреждает о подозрительном явном приведении типов. Программист создает объект класса с помощью оператора new, затем указатель на этот объект приводится к другому типу:

T *p = (T *)(new A);

Странное сооружение. Если класс A унаследован от T, то это приведение является избыточным, и его можно удалить. Если он не унаследован, то, скорее всего, это ошибка. Однако анализатор не до конца уверен, что это ошибка, и ставит эту диагностику на средний уровень. Такая конструкция действительно может показаться очень странной, но иногда это действительно правильно работающий код. Однако трудно привести какой-либо пример.

Гораздо опаснее, если программист создает массив элементов, а затем преобразует его в указатель на базовый класс:

Base *p = (Base *)(new Derived[10]);

В этом случае анализатор выдает предупреждение высокого уровня. Размер базового класса может быть меньше размера унаследованного класса, и тогда при доступе к элементу p [1] мы будем работать с неверными данными. Даже если теперь размер базового класса и унаследованных классов одинаков, этот код требует исправления. Некоторое время все может работать нормально, но очень легко все сломать, добавив новый член класса к классу наследования.

Противоположная ситуация, когда программист приводит к одному и тому же типу.

T *p = (T *)(new T);

Этот код может появиться, если кто-то слишком долго работал с C и забыл, что, в отличие от вызова функции malloc, обязательное приведение типа не требуется. Или в результате рефакторинга старого кода, когда программа на C превращается в C ++.

Здесь нет ошибки, и поэтому вообще нет необходимости выдавать предупреждение. Анализатор на всякий случай оставляет это предупреждение, но переводит его на уровень Low. Программисту не нужно просматривать это предупреждение и исправлять этот код; хотя, если он хочет, чтобы все выглядело красиво и аккуратно, он может это сделать.

В комментариях к предыдущей статье некоторые читатели беспокоились, что предупреждения, которые могут указывать на настоящую ошибку, могут исчезнуть из анализа. Как правило, такие предупреждения не исчезают, а переходят на низкий уровень. Мы только что рассмотрели один такой пример. «Т * р = (Т *) (новый Т);». Здесь нет ошибки, но что, если здесь что-то не так… Желающие могут изучить этот код.

Давайте посмотрим на другой пример. Диагностика V531: Странно, что оператор sizeof () умножается на sizeof ().

size_t s = sizeof(float) * sizeof(float);

Это бессмысленное утверждение, и, скорее всего, здесь была ошибка; наверное опечатка. Анализатор выдаст предупреждение высокого уровня.

Но бывает ситуация, когда уровень меняется на Низкий. Это происходит, когда один из множителей равен sizeof (char).

Из всех выражений «sizeof (T) * sizeof (char)», которые мы видели, в более чем сотне проектов они не были ошибками. Практически всегда это были какие-то макросы, где такое умножение было вызвано заменой одного макроса другим.

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

Рис. 7. Теперь читатель знает, что он может смело отправиться в путешествие по бескрайним морям предупреждений низкого уровня.

Исключения в диагностике

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

int *p = NULL;
if (p)
{
  *p = 1;
}

При разыменовании указателя его единственное возможное значение - NULL. Другого значения, которое можно было бы сохранить в переменной «p», нет. Но запускается исключение, что разыменование происходит в коде, который никогда не выполняется. И если он не запускается, значит, там нет ошибки. Разыменование произойдет только в том случае, если значение p имеет значение, отличное от NULL.

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

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

Теперь обратимся к некоторым частным исключениям в диагностике. Вернемся к нашей диагностике V572, которую мы обсуждали ранее:

T *p = (T *)(new A);

Есть исключения, когда это сообщение не выдается. Один из таких случаев - приведение к (void). Пример:

(void) new A();

Программист создает объект и намеренно оставляет его в коде до конца выполнения программы. Эта конструкция не могла появиться только из-за опечатки. Это преднамеренное действие для подавления предупреждений от компиляторов и анализаторов для операторов:

new A();

Многие инструменты будут жаловаться на эту конструкцию. Компилятор / анализатор подозревает, что человек забыл написать указатель, который будет возвращен оператором new. Таким образом, человек намеренно подавил предупреждения, добавив приведение к типу void.

Да, этот код странный. Но если человек просит оставить свой код в покое, он должен это сделать. Задача анализатора - искать ошибки, а не заставлять человека писать более сложные конструкции, запутывающие компилятор / анализатор, и избавляться от предупреждений.

Кому-нибудь будет полезно, если сообщение все-таки будет выдано? Нет. Человек, написавший этот код, не будет очень благодарен.

Теперь вернемся к диагностике V531:

sizeof(A) * sizeof(B)

Бывают ли случаи, когда анализатор не должен выдавать никаких предупреждений даже низкого уровня? Да это так.

Типичная задача: необходимо оценить размер буфера, размер которого кратен размеру другого буфера. Допустим, есть массив из 125 элементов типа int, и нам нужно создать массив из 125 элементов типа double. Для этого количество элементов массива необходимо умножить на размер объекта. Но очень легко ошибиться, оценивая количество элементов. Поэтому программисты используют специальные макросы для безопасного расчета количества элементов. Узнайте больше о том, почему и как это сделать, из статей (см. Здесь о макросе arrayysize).

После раскрытия макроса получаем следующую конструкцию:

template <typename T, size_t N>
char (*RtlpNumberOf( __unaligned T (&)[N] ))[N];
....
size_t s = sizeof(*RtlpNumberOf(liA->Text)) * sizeof(wchar_t);

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

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

Опять же, будет ли это кому-нибудь полезно, если анализатор все равно выдаст предупреждения? Нет. Этот код абсолютно правильный и надежный. Это должно быть написано так.

Давайте двигаться дальше. Анализатор выдаст предупреждение V559 за строительство.

if (a = 5)

Чтобы подавить предупреждение для такого кода, мы должны заключить выражение в дополнительные скобки:

if ((a = 5))

Это подсказка анализаторам и компиляторам, что здесь нет ошибки и человек хочет присвоить значение внутри условия. Я понятия не имею, кто и когда придумал такой метод; но я знаю, что это довольно распространено и поддерживается многими компиляторами и анализаторами.

Анализатор PVS-Studio тоже не пожалуется на этот код.

Возможно, нам следовало переместить это предупреждение на уровень Low, а не полностью его подавлять? Нет. Есть ли вероятность, что человек случайно заключит в скобки неправильное утверждение? Да, но очень маленький шанс.

Часто ли вы заключаете лишние скобки? Я так не думаю. Я думаю, что это происходит один раз на 1000 операторов if или даже реже. Таким образом, вероятность того, что указанная ошибка будет сделана из-за лишних скобок, меньше 1 из 1000.

Было бы неплохо сделать предупреждение? Нет. Это тот случай, когда можно избежать ложного срабатывания, и в то же время вероятность найти настоящую ошибку чрезвычайно мала.

Я уже приводил эти аргументы в комментариях к предыдущей статье, но они были недостаточно убедительны. Итак, я решил подойти к этой теме с другой стороны.

У меня вопрос к тем, кто хочет видеть все возможные предупреждения от анализатора. Вы покрыли 100% своего кода модульными тестами? Нет? Как так, у вас там могут быть ошибки!

Я также должен упомянуть здесь об этом; Очень сложно и дорого покрыть 100% кода модульными тестами. Стоимость этого покрытия для модульного тестирования не окупится затраченных усилий или времени.

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

Рассмотрим еще один случай, когда предупреждение V559 не выдается:

if (ptr = (int *)malloc(sizeof(int) * 100))

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

if (ptr == (int *)malloc(sizeof(int) * 100))

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

Будет ли какая-то практическая польза, если анализатор начнет выдавать предупреждения о таких конструкциях? Нет.

Завершим главу еще одним примером исключения. Объяснить это немного сложнее, но я постараюсь передать нашу философию относительно этого случая.

Диагностика V501 - один из лидеров по количеству исключений. Однако эти исключения не мешают правильной работе диагностики (доказательство).

Диагностика выдает предупреждения для таких операторов:

if (A == A)
int X = Q - Q;

Если левый и правый операнды совпадают, это подозрительно.

Одно из исключений гласит, что нет необходимости выдавать предупреждение, если операция «/» или «-» применяется к числовым константам. Примеры:

double w = 1./1.;
R[3] = 100 - 100;

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

Вот пример реального кода, содержащего такие утверждения:

h261e_Clip(mRCqa, 1./31. , 1./1.);

Можем ли мы пропустить ошибку из-за такого заявления? Да мы можем. Однако выгода от уменьшения количества ложных срабатываний намного превышает потенциальную потерю полезных предупреждений.

Такое деление или вычитание является стандартной практикой в ​​программировании. Риск потерять предупреждение оправдан.

Есть ли шанс, что программист намеревался написать другое утверждение? Да, есть. Но такие обсуждения ни к чему не приведут. Фраза «возможно, программист хотел написать что-то еще» применима к 1./31, так что здесь мы можем прийти к идее идеального анализатора, который выдает предупреждения для всех строк в программе, даже для пустых. . На всякий случай подумав, что это может быть неправильно или, возможно, следует вызвать функцию foo ().

Рис. 8. Важно остановиться на каком-то этапе. В противном случае полезная задача изучения предупреждений станет пустой тратой времени.

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

Еще раз краткие факты об исключениях

Полагаю, я дал довольно подробное объяснение, но предполагаю, что могу получить комментарий такого рода:

Не понимаю, зачем жаловаться на непонимание, вместо того, чтобы просто создать функцию и кнопку «включить / выключить». Если хотите, можете использовать - если нет, не используйте. Да, это требует некоторой работы. И да, это ваша работа.

Рисунок 9. Реакция единорога на настройку, отключающую все фильтры предупреждений.

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

В анализаторе такая кнопка уже есть! Это там! Он называется «Низкий» и отображает предупреждения с минимальным уровнем достоверности.

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

Я объясню это на примере диагностики V519. Он предупреждает, что одному и тому же объекту дважды подряд присваиваются значения. Пример:

x = 1;
x = 2;

Но диагностика так работать не может. Итак, мы должны внести определенные уточнения, такие как:

Исключение N1. Объект используется во втором операторе как часть правого операнда операции =.

Если исключить это исключение, анализатор начнет жаловаться на совершенно нормальный код:

x = A();
x = x + B();

Кто-нибудь хочет тратить время и силы на просмотр такого кода? Нет.

Так что нас сложно убедить в обратном.

Главная идея

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

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

Приведу аналогию. Безопасность во время строительства обычно обеспечивается различными методами: обучением технике безопасности, ношением шлемов, запретом работать в нетрезвом виде и так далее. Было бы неэффективно выбирать только один компонент и надеяться, что он решит все проблемы. Можно сделать замечательный бронированный шлем, а то и каску со встроенным счетчиком Гейгера и водопроводом на день; но это не спасет вас от падения при работе на высоте. Здесь вам понадобится еще одно приспособление - страховочная веревка. Вы можете подумать о парашюте, встроенном в шлем. Это, конечно, интересная инженерная задача, но такой подход непрактичен. Скорее всего, вес и размер шлема превысят все разумные пределы. Шлем замедлит работу и будет неудобно носить. Есть вероятность, что строители тайком снимут шлем и будут работать без него.

Если пользователю удалось обработать все предупреждения анализатора, нет смысла пытаться увидеть как можно больше предупреждений низкого уровня. Было бы полезнее работать над модульными тестами, чтобы покрыть код как минимум на 80%. Я даже не предлагаю иметь 100% покрытие модульным тестированием, потому что время, необходимое для его создания и поддержки, перевешивает преимущества. В дальнейшем вы можете добавить в процесс тестирования кода один из динамических анализаторов. Некоторые типы дефектов, которые могут найти динамические анализаторы, не могут быть обнаружены статическими анализаторами. И наоборот. Вот почему динамический и статический анализ так идеально дополняют друг друга. Вы также можете разрабатывать тесты пользовательского интерфейса.

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

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

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

Вывод

Что мы узнали из этой статьи:

  • PVS-Studio старается искать не просто «запахи», а реальные ошибки; те, которые на самом деле могут привести к некорректной работе программы.
  • Диагностические сообщения делятся на три уровня достоверности (надежности): высокий, средний, низкий.
  • Мы рекомендуем просматривать только предупреждения о высоком и среднем уровнях.
  • Для тех, кого беспокоит возможность удаления полезной ошибки из-за исключения: это очень маловероятно. Скорее всего, такое ненадежное предупреждение перенесено на уровень Low. Вы открываете вкладку Low и просматриваете такие предупреждения.
  • В диагностике исключения неизбежны, иначе средство принесет больше вреда, чем пользы.

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

Unicorn будет и дальше обеспечивать качество вашего кода. Желаю вам всего наилучшего и предлагаю ознакомиться с презентацией PVS-Studio 2017 (YouTube).