Старые большие проекты

Постепенно и незаметно складывается ситуация, когда сложность серьёзных C++ проектов становится запредельной. К сожалению, теперь C++ программист не может полагаться только на свои силы.

Во-первых, кода стало так много, что уже невозможна ситуация, когда в проекте есть хотя бы парочка программистов, которые знают проект целиком. Например, ядро Linux 1.0.0 содержало около 176 тысяч строк кода. Это много, но была возможность поставить рядом кофе-машину и за пару недель более или менее просмотреть весь код и понять общие принципы его работы. Если же брать ядро Linux 5.0.0, то размер кодовой базы составляет уже около 26 миллионов строк кода. Код ядра вырос почти в 150 раз. Можно только выбрать несколько частей проекта и принимать участие в их развитии. Невозможно сесть и разобраться, как именно это всё работает, какие есть взаимосвязи между различными участками кода.

Во-вторых, язык C++ продолжает быстро развиваться. С одной стороны, это хорошо, так как появляются конструкции, которые позволяют писать более компактный и безопасный код. С другой стороны, в силу обратной совместимости, старые большие проекты становятся разнородными. В них переплетаются старые и новые подходы к написанию кода. Напрашивается аналогия с кольцами на срезе дерева. Из-за этого с каждым годом погружаться в проекты, написанные на C++, становится всё сложнее и сложнее. Нужно одновременно разбираться в коде, как написанном ещё в стиле C с классами, так и в современных подходах (лямбдах, семантике перемещения и т.д.). Чтобы всесторонне изучить С++ требуется слишком много времени. Но поскольку развивать проекты все равно требуется, люди начинают писать код на C++, так и не изучив до конца всех его нюансов. Это приводит к дополнительным дефектам, но нерационально из-за этого остановиться и ждать, когда все разработчики станут идеально знать C++.

Ситуация безнадёжна? Нет. На помощь приходит новый класс инструментов: статические анализаторы кода. Многие бывалые программисты в этот момент скривили губы, как будто им подсунули лимон :). Мол, знаем мы эти ваши линтеры… Сообщений много, толку мало… Да и какой же это новый класс инструментов?! Мы линтеры ещё 20 лет назад запускали.

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

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

Однако намного важнее, что современные статические анализаторы обладают обширными знаниями по паттернам ошибок. Причем анализаторы знают больше, чем даже профессиональные разработчики. Слишком сложно стало учитывать и помнить все нюансы при написании кода. Например, если специально про это где-то не прочитать, то вы никогда не догадаетесь, что вызовы функции memset для затирания приватных данных иногда исчезают, так как с точки зрения компилятора вызов функции memset избыточен. А между тем, это серьезный дефект безопасности CWE-14, который обнаруживается буквально везде. Или кто, например, знает, что опасного в подобном наполнении контейнера?

std::vector<std::unique_ptr<MyType>> v;
v.emplace_back(new MyType(123));

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

Помимо обширного знания паттернов статические анализаторы бесконечно внимательны и никогда не устают. Например, в отличие от человека, им не лень заглянуть в заголовочные файлы, чтобы убедиться, что isspace и sprintf — это настоящие функции, а не безумные макросы, которые всё портят. Подобные случаи демонстрируют суть сложности поиска ошибок в больших проектах: меняется что-то в одном месте, а ломается в другом.

Я убеждён, что в скором времени статический анализ станет неотъемлемой частью DevOps — это будет столь же естественно и необходимо, как использование системы контроля версий. Это уже постепенно происходит и на конференциях, посвященных процессу разработки, где всё чаще упоминается статический анализ как одна из первых линий обороны для борьбы с багами.

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

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

Мир предлагает большое количество инструментов статического анализа кода. Как говорится, выбирайте на свой вкус. Хотите находить множество ошибок и потенциальных уязвимостей ещё на этапе написания кода? Используйте статические анализаторы кода и улучшите качество вашей кодовой базы!



Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать эту ссылку: Andrey Karpov. Why Static Analysis Can Improve a Complex C++ Codebase

Комментарии (24)


  1. khim
    04.08.2019 00:02
    +6

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

    P.S. И да, я, в отличие от многих, не призываю вас это сделать «прям щаз», а то «я на вас обижусь». Как раз то, что продажа сложных продуктов, требующих внедрения начинается со «звонка оператору» — это нормально. Просто это показатель того, что «это вот — всё ещё продукт для избранных, не для всех».


  1. maaGames
    04.08.2019 07:00
    +3

    В новом (уже не очень новом) стандарте у меня иногда случаются проблемы при переводе старого кода на новый формат. Например, отслеживает ли PVS-Studio вот такие ситуации? (разумеется, в виде списке предупреждений, а не ошибок). У меня сейчас проблемы в работе VisualStudio, не могу PVS использовать, чтобы самому проверить.

    std::vector<size_t> v1( 10, 1 ); // размер 10, инициализирован единицами
    std::vector<size_t> v2{ 10, 1 }; // размер 2, заполнен числами 10 и 1
    


    Уже были ситуации, когда, по невнимательнсоти, писал не те скобки. Если не повезёт с типами, то даже скомпилируется…


    1. Andrey2008 Автор
      04.08.2019 10:58

      Вопрос не понятен. А что хочется детектировать? Что используется такой {} или такой () формат инициализации? Не очень хорошая идея для диагностики…
      Прошу привести кратное описание желаемых диагностик с примерами.


      1. maaGames
        04.08.2019 11:08
        +1

        Выводить предупреждение минимального «уровня опасности»… Хотя… Невозможно же определить, какой из двух вариантов хотел написать программист и тогда предупреждений будет бесполезно огромное количество… Понимаю, плохая идея. Надо просто для списка инициализации всегда писать "= {...}", тогда вероятность ошибки будет минимизирована.


        1. khim
          04.08.2019 14:05

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

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

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

          Может даже оказаться, что два элемента в списке инициализации — уже сама по себе неплохая эвристика…


          1. maaGames
            04.08.2019 14:11

            Теоретически, если сперва был список инициализации, а потом обращение к вектору на запись значений, особенно циклом, почти со 100% вероятностью скажут про ошибку задания, что вместо круглых скобок поставили фигурные.
            К счастью, этот код скомпилируется только для целых чисел и в большинстве случаев оибка обнаружится при первом же запуске. Н опридётся долбиться в глаза, тчобы различие скобочек увидеть. Переживу :)


    1. ianzag
      04.08.2019 11:07
      +1

      Оба кусочка кода синтаксически и семантически совершенно валидны. Допустим, после мы имеем нечто навроде:

      for (const auto i : v1) {
          std::cout << std::dec << i << std::endl;
      }
      

      Как вы предлагаете догадаться анализатору что на самом деле хотел сделать автор? Почему, допустим, версия с v1 должна считаться ошибкой а v2 нет или наоборот :-?


      1. khim
        04.08.2019 14:08

        Потому что в вашем случае вообще нужно бы std::array использовать или прямо так написать:

          for (const auto i : {1, 2}) {
              std::cout << std::dec << i << std::endl; 
          }
        

        А вообще синтаксичейский анализатор тем и отличается от компилятора, что он пытается обнаружить ошибки в синтаксически валидном коде…


  1. JekaMas
    04.08.2019 12:10
    +2

    Белой завистью завидую мощи инструментов! К сожалению, до мира golang нескоро подобное дойдёт. Но даже прикручивание пачки линтеров в CI даёт отличные результаты: уменьшается нагрузка на code review, появляется объективное обоснование исправления кода с ошибкой или небезопасным поведением, что важно для некоторых товарищей, легче вводить людей в проект. Вообще "всё, что может быть автоматизировано, должно быть автоматизировано".


  1. oleg-x2001
    04.08.2019 20:25
    -10

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


    1. snamef
      04.08.2019 22:47
      -3

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

      многим ошибкам быть в валидном коде
      то виноват язык.


      1. KanuTaH
        05.08.2019 00:16

        Правильно ставят. Язык — это не более, чем инструмент, а любым инструментом нужно уметь пользоваться. Выстрелить себе в ногу можно хоть на C++, хоть на D — не одним способом, так другим, голову необходимо прикладывать в любом случае. Было бы ошибкой думать ©, что можно просто взять говнокодера, кое-как тяп-ляп обученного C++, «по-быстрому» переобучить его на D, и вуаля — он больше не будет плодить говнокод. Как бы не так, это так не работает.


        1. oleg-x2001
          05.08.2019 13:37
          -2

          Да, язык это инструмент и любым инструментом нужно уметь пользоваться, вот только зачем тратить уйму времени на изучение кривого, уродливого и ненадежного инструмента когда то же самое можно сделать гораздо меньшими усилиями с помощью более простого, изящного и надежного инструмента? Скажем, template metaprogramming, compile-time programming — сложные, но полезные штуки. Пробовали когда-нибудь использовать их для чего-нибудь маломальски содержательного на С++? В D использование этих подходов ничуть не сложнее «обычного» программирования. То есть целого класса проблем просто не существует. И речь не о том что говнокодер на С++ переучится в профессионала начав кодить на D. Речь о том что большинство программистов станут более эффектво решать свои непосредственные проблемы, а не тратить свои силы, свое время и свой талант на борьбу с уродством и убогостью С++.


          1. KanuTaH
            05.08.2019 14:36
            +1

            Ну, зато они начнут "бороться с уродством и убогостью" D, например, допиливать местный gc, без которого сейчас в D по большому счету особо не обойтись. Это вообще одно из основных больных мест D, регулярно поступают предложения сделать хотя бы ARC а-ля поздний objc/Swift, но пока воз и ныне там. Короче, серебряной пули, как обычно, не существует.


            1. oleg-x2001
              05.08.2019 14:44
              -2

              Да я и не утверждаю что D идеален. Но глупо отрицать что он на голову выше С++ по своему дизайну (и так же глупо было бы утверждать что он вообще лишен недостатков). Моя мысль сводится к тому что самое разумное для сообщества было бы ивестировать свои силы в то что можно улучшить, например в D. И бессмысленно тратить силы на то что улучшить уже нельзя. Любое «улучшение» С++ (= приделывание костылей) лишь будет сильнее затягивать его в пучину переусложненного синтаксиса и нагромождения разнородных идей, криво сшитых белыми нитками.


              1. khim
                05.08.2019 15:10
                +1

                Моя мысль сводится к тому что самое разумное для сообщества было бы ивестировать свои силы в то что можно улучшить, например в D.
                Зачем?! Языков с GC — как грязи. Популярных, хороших, используемых: C#, Go, Java…

                Вы же вроде про замену C++ вякали? Ну так тут GC должен отсутствовать. Пока что в этой нише один серьёзный игрок: Rust.

                Его потихоньку пробуют… Но заменить C++ он пока не готов…

                Любое «улучшение» С++ (= приделывание костылей) лишь будет сильнее затягивать его в пучину переусложненного синтаксиса и нагромождения разнородных идей, криво сшитых белыми нитками.
                Поживём — увидим. Пока что C++ успешно пережил с полдюжины попыток «замены». Часть — ушла в небытиё, часть — заняла какую-то нишу. Но вытеснить C++ никому пока не удалось…


                1. oleg-x2001
                  05.08.2019 15:21
                  -1

                  Да, я вякал про замену С++ ) D это наиболее близкий к С++ язык из всех. Java это такое же уродливое говно как и С++, да области примениния у них разные, так что Java и С++ не конкуренты. Равно как не конкуренты С++ и Go (по крайней мере, не во всем). С#, да и вообще вся платформа .NET, это замечательная вещь, и для все будет лучше если С# вытеснит Java, но, опять же C# и С++ не конкуренты.

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


                  1. KanuTaH
                    05.08.2019 15:42

                    Думаю, сообщество само разберётся, без ценных указаний класса «взять все и поделить переписать», а все эти человеко-леты потрачены отнюдь не зря, чего не скажешь о человеко-летах, потраченных на несостоявшихся «убийц» C++.


                    1. oleg-x2001
                      05.08.2019 15:47
                      -1

                      То есть по существу вопроса сказать больше нечего? )


                      1. KanuTaH
                        05.08.2019 15:58
                        +1

                        Пффф, а что вообще можно сказать по существу предложений типа "имею мнение, что язык X говно, давайте его выкинем и перепишем все на Y"? Это фанбойский тип мышления, спорить с ним бесполезно. Всё, что я хотел сказать по существу, я уже выше сказал.


                        1. oleg-x2001
                          05.08.2019 16:07
                          -2

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


                          1. Am0ralist
                            05.08.2019 16:10

                            Вообще-то, судя по треду, это вы влезли в дискуссию между ним и snamef…


    1. Cerberuser
      05.08.2019 10:09

      Я сам в последнее время изрядно зафанател от того же Rust, но — нельзя просто так взять и перестать использовать C++, особенно в достаточно больших проектах. Начать понемногу внедрять альтернативный язык — да (и то, взаимодействие кода на C++ с другими языками — это порой та ещё головная боль). Но не более, по крайней мере, на начальном этапе.