Тема статических анализаторов кода изъезжена. Последние полгода практически нет конференции без выступления, посвященного этому вопросу. Но все они рассказывают об анализаторах изнутри, показывают механизмы их работы. При этом забывают грамотно объяснить, для чего они изначально предназначены. Чаще всего рассматривается теоретическая часть работы статических анализаторов, без практической реализации. Поэтому расскажу про цели, которые преследуются статическими анализаторами в реальных командах на коммерческих проектах. А также рассмотрю пример организации работы с различного рода анализаторами в нашей компании.
Что такое статический анализ
Это анализ программного кода, проводимый без его запуска. Поэтому ошибки run-time работы программы отловить с его помощью нельзя. Статический анализ работает лишь с текстовым кодом программы, что занимает несколько секунд на запуск, но экономит часы при нахождении багов.
Существует несколько методик, по которым работают анализаторы.
- Поиск на основе регулярных выражений. Здесь всё предельно просто. Находите в коде текст, который попадает под регулярку, и вам предлагается лучший вариант.
- Анализ грамматики языка. Более сложный вариант разбора кода. Грамматика представляется в виде дерева, вершинами которого являются операторы языка, а листья — операндами. Позволяет находить гораздо более сложные конструкции по сравнению с регулярными выражениями. Более сложен в реализации, поэтому большинство анализаторов представляет возможность написания кастомных правил именно на основании регулярок, хотя внутри могут работать как синергия первого и второго. Для Swift на момент написания мною не было найдено возможности создания кастомного правила на основании синтаксического дерева.
Зачем внедрять статический анализ
Анализаторы нужны, чтобы находить баги на максимально раннем этапе. В нашем случае — это компиляция проекта. Чем позже будут найдены баги, тем дороже они обойдутся разработчику и бизнесу в целом.
Однако, эта цель слишком общая и не несет особой смысловой нагрузки. Попробуем декомпозировать её на несколько мелких:
- Нахождение критичных багов в коде. К примеру, различные force unwrap, там где они не нужны. При этом есть различные утилиты, которые позволяют находить более сложные вещи, такие как циклические ссылки.
- Автоматизация работы styleguide. В командах для «обезличивания кода» одной из методик является внедрение styleguide, в котором прописываются основные практики написания кода. Часто получается огромный документ на десятки страниц. Держать их в голове становится проблематично, особенно, когда в команду приходит новый разработчик, который ранее писал в другом стиле. И именно здесь отлично подходит статический анализатор. Он позволяет автоматизировать большинство правил, которые записаны в вашем styleguide. В языках, которые появились ранее Swift’a, в styleguide для каждого правила обычно указывается, добавлено ли оно в linter. И если добавлено, то под каким именем. Вот пример для js, как это реализовывать.
Как реализовать кастомные правила
Самым популярным на данный момент анализатором кода для Swift является swiftlint. Мы также используем tailor. Первый позволяет создавать свои собственные правила на основе регулярных выражений, что дает возможность реализовать большинство правил из вашего styleguide.
Swiftlint позволяет создавать кастомные правила, которые необходимо поместить в конфигурационный файл .swiftlint.yml, находящийся в вашем проекте. У языка описания кастомных правил есть ряд параметров.
- included \ Включает определенные файлы. Работает как регулярное выражение. Пример: ".*.swift". Является опциональным.
- excluded \ Исключает определенные файлы. Работает как регулярное выражение. Пример: ".*Test.swift". Является опциональным.
- name \ Наименование правила. Является опциональным.
- regex \ Регулярное выражение для поиска.
- rule identifier \ Идентификатор правила. Является опциональным.
- match_kinds \ Тип кода, в котором будет происходить поиск. Их достаточно много, к примеру можно искать только в комментариях или ключевых словах. Является опциональным.
- message \ Сообщение, которое выводится при нахождении регулярным выражением данного правила
- severity \ Бывает двух типов: warning/error. В зависимости от выбранного значения будет показываться в Xcode как ошибка или предупреждение
К примеру, в вашей компании вы используете только weak и не используете unowned. Производительность последнего выше, чем первого. Но то, что weak не приведет к падению приложения, для вас более важно. Это легко можно сделать с помощью регулярного выражения и создать кастомное правило.
unowned:
name: "Unowned"
regex: 'unowned'
message: "Please use `weak` instead. "
severity: error
Еще один пример про комментарии. Допустим, вы не пишете хедеры у себя в проекте. Регулярное выражение также идеально с этим справится.
no_header_comments:
name: "Header Comments"
regex: '//\s*Created by.*\s*//\s*Copyright'
match_kinds:
- comment
message: "Template header comments should be removed."
Как реализовать систему анализаторов кода
Самая интересная часть заключается в масштабировании и автоматизации процесса статического анализа. Особенно это важно, если проектов несколько и хочется, чтобы все кастомные правила были одинаковы для них. Все анализаторы имеют некий конфигурационный файл, в котором и хранятся настройки. В наших проектах таких файлов три: .swiftlint.yml, .tailor.yml, cpd_script.php.
Последний файл является скриптом, написанным на php. Он запускает анализ файла, выдаваемого детектором копипасты в свифтовом проекте. О его настройке расскажу в следующей статье.
Простейшим вариантом поддержки будет создание отдельного репозитория для данных файлов. Мы делаем вот так. Подключить к проекту можно через submodule. Если настройки в файлах конфигурации обновляются, то достаточно обновить submodule. Тогда в вашем проекте будут актуальные правила.
Кроме этого, вы можете пользоваться некоторыми дополнительными функциями github. Мы используем систему issues для создания новых кастомных правил или обсуждения текущих. Это позволяет понять, почему было отклонено то или иное правило. Удобно отслеживать статус, чтобы вносить изменения в файлы конфигурации.
Эта система прекрасно работает на CI. Его можно настроить таким образом, чтобы все pull requests в вашем проекте нельзя было смержить, если анализаторы нашли хотя бы один недочет в коде.
Не забудьте поддерживать систему анализа
Статический анализ — один из самых ранних способов нахождения ошибок в приложении. Как следствие, самый дешевый. Надеюсь, что простейшая система для хранения конфигурационных файлов статических анализаторов поможет вам сделать проекты «одинаковыми». А еще позволит найти не один баг до этапа тестирования.