В этой статье рассмотрим современные инструменты, посредством которых можно улучшить старые проекты на С++. Благодаря новейшим компиляторам и оптимизаторам можно перелопатить кучу кода, не тратя на это полжизни.
Введение
Приходилось ли вам встречать ужасные строчки вроде этих?
Заметить ошибку нетрудно. Даже базовый компилятор услужливо предупредит о возможной потере данных при конвертации float в int . Но что делать с более сложным кодом в работающем проекте?
Visual Studio 2019 имеет встроенный анализатор кода, который будет давать довольно полезные подсказки. Рассмотрим следующий пример:
В VS 2019 мы можем настроить правила проекта под свои нужды. Включить все пункты или создать детальный профиль.
При включении анализатора сразу получаем ряд предупреждений.
Сначала для класса SuspiciousType :
А потом и в функции main :
Как видите, среда разработки успешно обнаружила все существенные недочёты. Более того, если предупреждение связано с несоблюдением гайдлайна – в конце будет указан конкретный пункт, который легко поправить, найдя его в официальном руководстве.
Как бонус, Visual Studio теперь подчёркивает зелёной волнистой линией элементы когда, которые считает устаревшими или сомнительными. При клике на код показываются подобные комментарии.
Если вы не используете последнюю версию Visual Studio, то обратите внимание на Clang Power Tools. Это расширение для студии даёт приблизительно такой же набор функций.
Но разбирать откровенно дурной код это одно, а можно ли извлечь пользу из этих инструментов на реальном проекте?
Рассмотрим проект покрупнее
В декабре 2019-го я открыл свой старый проект со времён учёбы. Это визуализация алгоритмов сортировки, написанная в далёких 2005/2006 годах на старом С++, Win32Api и OpenGL. Код можно посмотреть в репозитории.
Программа принимает на вход массив значений и обрабатывает их со скоростью около 30 действий в секунду. Каждый элемент отрисовывается на диаграмме. Зелёным обозначается элемент, к которому производится обращение в данный момент, а голубым – сортируемая в данный момент часть массива. Демонстрацию на примере Quick sort вы видите в гифке выше.
Несмотря на красивый внешний вид, внутри есть ряд ужасных идей, так что не пугайтесь, если решите изучить репозиторий.
Это довольно интересный опыт – изучать свой код, написанный 15 лет назад. Я решил переделать проект под VS 2019. Чтобы вспомнить, как всё работало, я реализовал Quick sort, которого изначально в программе не было.
Приложение использует С++ 03 и достаточно велико. Так что это отличный пример, для демонстрации вариантов модернизации старых проектов.
Сообщения о проблемах
Когда я включил анализатор кода на полную, то получил 956 предупреждений. Этого стоило ожидать, давайте рассмотрим их подробнее.
Использование констант
Компилятор видит переменные, не меняющие значение в процессе выполнения, и автоматически предлагает использовать константы.
Например, для такого кода:
Предупреждение выглядит так:
Это касается и функций:
Неинициализированные переменные
К сожалению, тогда в моём коде это было распространённой ошибкой. Например, для CGLFont я забыл про m_fSize .
И получил такое предупреждение:
Избыточное использование указателей
В 2005 я не особо много знал об умных указателях и везде использовал new и delete .
В современном C++ мы должны избегать такого подхода. VisualStudio заботливо найдёт места, нуждающиеся в модернизации:
К тому же компилятор может находить проблемы с null pointer. Например, в таком коде, я получил уведомление о непроверенном на null указателе:
А дальше мне надо решить, добавить проверку или пометить указатель, как not_null.
Переход на nullptr
В этом нет ничего сложного, заменить все NULL из моего кода на nullptr из C++ 11 – тривиальная задача. Clang-tidy даже может сделать это автоматически.
Использование noexept
В C++ 11 мы получили спецификатор noexept для оптимизации генерируемых бинарных файлов. Естественно, в своём старом коде я этого использовать не мог и получил кучу предупреждений.
Например, для такого кода:
VisualStudio дала следующее предупреждение:
И да, геттеры должны быть константными.
Больше noexept
Иногда добавлением спецификатора проблему не решить. Приходится рассматривать вариант полного обновления функции. Вот что я получил:
Использование override
С override похожая история. В 2005 у нас не было такого модификатора, поэтому для интерфейса, определяющего три чисто виртуальные функции:
у меня не было возможность выразить это в производном классе и приходилось писать так:
C++ 11 дала нам возможность это изменить:
Правило нуля
По некой мистической причине я сделал кучу пустых деструкторов, и компилятор это заметил:
Заключение
Возвращаться к своим древним проектам довольно весело, особенно если вам нравятся их идеи. Удивительно, как с годами меняются инструменты и походы к решениям. Современные компиляторы и средства анализа могут быть отличными напарниками и делать неплохое базовое ревью кода. Иногда даже указывать на места, где стоит подтянуть теорию современной разработки.
Конечно, вы можете обновить весь свой код, полагаясь только на свою «силу», знания и опыт, но ведь можно использовать современные инструменты и сохранить кучу времени? Visual Studio только один из многих.
А вы рефакторите свой старый код? Какие инструменты используете для этого?