Привет всем, меня зовут Коношенко Владислав, и я Flutter-разработчик из Surf. Сегодня я хотел рассказать и показать с помощью каких инструментов можно делегировать компьютеру рутинную работу по анализу и рефакторингу кода в кратчайшие сроки. Уверяю вас, что уже после первого применения этих знаний, вы избавитесь от тонны повторяющихся замечаний на ревью кода. Также посмотрим, как расширять возможности анализатора кода и делиться своими наработками с комьюнити. Вдохновил меня на участие в данном проекте мой тимлид Дмитрий Круцких. Собственно, благодаря ему, вы и читаете эту статью.
Что такое Dart Code Metrics
Думаю, многие начинающие разработчики (и не только начинающие) хотели бы иметь наставника, который регулярно делает code review и указывает на неточности и ошибки в написании кода. Но, к сожалению, мир не идеален, и даже самые опытные разработчики могут ошибаться. Да, и к тому же, поставив себя на их место ,можно почувствовать всю боль этой монотонной работы. Представьте такого ревьювера, который не имеет усталости, у него нет плохого настроения и , что самое главное, он доступен 24/7. Утопия!? Нет, это всем вам знакомый статический анализатор кода. В этот статье речь пойдет о Dart Code Metrics(DCM). Этот инструмент позволяет заблаговременно предупредить о возможных ошибках, а также исправить уже существующие проблемы. Это незаменимый инструмент, который начнет приносить пользу проекту с первых минут его подключения. Все правила можно гибко сконфигурировать под требования проекта, также при возникновении проблем всегда может помочь активное комьюнити в телеграм.
Подробное описание о том, как именно это работает и как настраивать анализатор можно получить в этой статье. Мы поговорим о том, как создавать свои правила и как можно расширять набор правил для DCM и таким образом поднимать качество кода всего комьюнити.
Мои первые шаги в Open Source
Проект является полностью Open Source. Это значит, что исходный код его полностью открыт для пользователей, а, следовательно, его можно доработать под свои задачи. А эти решения уже потом могут помочь другим.
Dart Code Metrics стал первым проектом, в который я сделал конрибьют, создав новое правило. Стоит отметить, что если вы также , как и я, ранее ничего не контрибьютили, и вас останавливает непонимание того, как это работает, то вам будет полезно ознакомиться с этой пошаговой инструкцией, которая развеет все страхи.
Развиваем dart-code-metrics
Создаем новую Issue
Помощь в разработке опенсорса может быть не только в написании нового кода, а также в участии обсуждений той или иной фичи или создании issue с предложением об улучшении продукта.
Разберемся с созданием новой issue. Это можно сделать в разделе Issue на github. У DCM есть 4 базовые issue:
-
Bug report
-
New rule proposal
-
Question
-
Rule change proposal
В каждом из этих вариантов содержится шаблонная форма для заполнения, которая позволит другим разработчикам четко понять характер проблемы и какими способами ее лучше решить.
Немного о культуре создания issue. При создании старайтесь предоставить максимальный набор информации, даже если вам кажется, что информация избыточна, то для другого специалиста она может быть действительно важна. Держите в голове мысль о том, что сэкономленные вами 10 минут при создании Issue могут потратить несколько часов другого человека. Ну, и самое главное: при четко сформулированных требованиях вероятность того, что ваш баг или предложение рассмотрят быстрее — намного выше. Вот пример хорошего сообщения.
Делаем первый PR
Первое, что мы должны сделать как порядочные контрибьюторы — это почитать информацию, которую нам оставили предыдущие разработчики. Обычно она лежит в корне репозитория в файле CONTRIBUTING.md. В этом документе мы получаем базовую информацию для понимания следующих работ с пакетом:
-
Создание pull request(PR)
-
Как создавать свое правило.
-
Как запустить проект в IDE.
Вот мы познакомились с базовыми правилами. Дальше я расскажу, с чего можно начать. Если у вас есть конкретные предложения, то Вы можете попробовать реализовать их самостоятельно, либо оставить свои предложения в виде Issue. Или Вы можете найти подходящую задачу во вкладке Issue. Изучая этот список, можно найти что-то интересное для себя. В поиске могут помочь механизмы github по названию label (аналог тегов или меток). Обратите внимание на очень хороший label для начинающих — good first issue.
После того, как вы выбрали задачку по своим силам, будет правилом хорошего тона — сообщить в issue о том, что беретесь за нее, чтобы избежать работы нескольких человек над одним и тем же. Но при этом, если по каким-то причинам перестаете этим заниматься, то отобразите это в issue: с какими проблемами столкнулись , какие варианты решений пробовали. Перед началом работы обязательно проверьте отсутствие PR на это Issue. Когда все это проверили, то делаете fork и далее по инструкции из first_contribute.
Пример написания правила
Почему это нужно
Историю моего PR можно посмотреть здесь. Прежде чем погрузить вас в написание кода, хочу сразу выделить преимущества контрибьютинг dart-code-metrics:
-
Это возможность получить code review от других специалистов. (долгое время работал в компании, где занимался проектами самостоятельно и такого опыта действительно не хватало).
-
Подобная работа позволяет расширить свой кругозор и понимание того, как устроен язык.
-
Возможность автоматизировать проверку каких-то базовых вещей в коде.
-
Приводит стиль кода к единому виду, что позволяет быстрее ориентироваться в чужом коде.
-
Один раз потратив время на написание правил, вы решаете не только свою проблему, но и помогаете найти такие проблемы другим пользователя DCM.
Пишем код
Основный функционал DCM — это правила для статического анализа. Для примера мы возьмем мой реальный PR с правилом, которое проверяет соответствие названий файла и содержимого в нем класса.
Для написания правила нам нужен класс самого правила, который будет наследником Rule и класс визитор, который является наследником одного из базовых классов(BreadthFirstVisitor, DelegatingAstVisitor, RecursiveAstVisitor, SimpleAstVisitor, TimedAstVisitor, ThrowingAstVisitor, UnifyingAstVisitor). Их можно выбирать в зависимости от того, какие ноды вы хотите проверять. Выбрать подходящий базовый класс можно изучив список абстрактных методов данных классов.
В visitor нам нужно найти все места, в которых объявляем класс. При этом, если в файле более одного класса, то мы должны брать первый публичный.
Последовательность:
-
Выбираем тип визитора
-
Переопределить нужные нам абстрактные методы для поиска нодов.
-
Собираем все необходимые нам ноды в массив.
class _Visitor extends RecursiveAstVisitor<void> { final _declarations = <SimpleIdentifier>[]; Iterable<SimpleIdentifier> get declaration => _declarations..sort(_compareByPrivateType); @override void visitClassDeclaration(ClassDeclaration node) { super.visitClassDeclaration(node); _declarations.add(node.name); } int _compareByPrivateType(SimpleIdentifier a, SimpleIdentifier b) { final isAPrivate = Identifier.isPrivateName(a.name); final isBPrivate = Identifier.isPrivateName(b.name); if (!isAPrivate && isBPrivate) { return -1; } else if (isAPrivate && !isBPrivate) { return 1; } return a.offset.compareTo(b.offset); } }
Следующий шаг — это написание самого правила. В этом классе мы задаем идентификатор правила, ошибку которого будем выводить в консоль. Также тут пишем код, который сравнивает имя файла и имя класса. И собственно, главная часть — это переопределение метода check в классе правила. В нем мы создаем экземпляр класса Visitor, созданный нами на предыдущем шаге, и обрабатываем полученные из него ноды. Если имя файла не соответствует имени класса, то создаем Issue и заполняем массив. Issues хранят информацию о том, где расположен код, какой тип ошибки, сообщение об ошибке, а иногда даже могут содержать hotFix.
Последовательность:
-
Задаем имя, идентификатор правила.
-
Переопределить метод check
-
Получаем список нод из Visitor
-
Пишем код, который проверяет ошибки
-
Проверяем на ошибки ноды.
-
Заполняем массив ошибок, если такие имеются
class PreferMatchFileName extends Rule { static const String ruleId = 'prefer-match-file-name'; static const _notMatchNameFailure = 'File name does not match with first class name'; static final _onlySymbolsRegex = RegExp('[^a-zA-Z0-9]'); PreferMatchFileName([Map<String, Object> config = const {}]) : super( id: ruleId, documentation: const RuleDocumentation( name: 'Prefer match file name', brief: 'Warns when file name does not match class name.', ), severity: readSeverity(config, Severity.warning), excludes: readExcludes(config), ); bool _hasMatchName(String path, String className) { final classNameFormatted = className.replaceAll(_onlySymbolsRegex, '').toLowerCase(); return classNameFormatted == basenameWithoutExtension(path) .replaceAll(_onlySymbolsRegex, '') .toLowerCase(); } @override Iterable<Issue> check(InternalResolvedUnitResult source) { final visitor = _Visitor(); source.unit.visitChildren(visitor); final _issue = <Issue>[]; if (visitor.declaration.isNotEmpty && !_hasMatchName(source.path, visitor.declaration.first.name)) { final issue = createIssue( rule: this, location: nodeLocation( node: visitor.declaration.first, source: source, withCommentOrMetadata: true, ), message: _notMatchNameFailure, ); _issue.add(issue); } return _issue; } }
Покрываем правило тестами
Для того, чтобы протестировать правило, создадим файл с тестом и рядом директорию с examples. В эту папку нужно положить файлы для теста. Мы проверяем инициализацию правила. Далее проверим файл без ошибок на отсутствие ложноположительных срабатываний. И самое главное — проверка файла с неправильным именем класса на то, что линтер действительно обнаружил ошибку.
void main() { group('PreferMatchFileName', () { test('initialization', () async { final unit = await RuleTestHelper.resolveFromFile(_withSingleClass); final issues = PreferMatchFileName().check(unit); RuleTestHelper.verifyInitialization( issues: issues, ruleId: 'prefer-match-file-name', severity: Severity.style, ); }); test('reports no issues', () async { final unit = await RuleTestHelper.resolveFromFile(_withSingleClass); final issues = PreferMatchFileName().check(unit); RuleTestHelper.verifyNoIssues(issues); }); test('reports about found issues for incorrect class name', () async { final unit = await RuleTestHelper.resolveFromFile(_withIssue); final issues = PreferMatchFileName().check(unit); RuleTestHelper.verifyIssues( issues: issues, startOffsets: [6], startLines: [1], startColumns: [7], endOffsets: [13], messages: ['File name does not match with first class name'], locationTexts: ['Example'], ); }); test('reports no issues for statefull widget class', () async { final unit = await RuleTestHelper.resolveFromFile(_withStateFullWidget); final issues = PreferMatchFileName().check(unit); RuleTestHelper.verifyNoIssues(issues); }); test('reports no issues for empty file', () async { final unit = await RuleTestHelper.resolveFromFile(_emptyFile); final issues = PreferMatchFileName().check(unit); RuleTestHelper.verifyNoIssues(issues); }); test('reports no issues for file with only private class', () async { final unit = await RuleTestHelper.resolveFromFile(_privateClass); final issues = PreferMatchFileName().check(unit); RuleTestHelper.verifyNoIssues(issues); }); test('reports no issues for file multiples file', () async { final unit = await RuleTestHelper.resolveFromFile(_multiClass); final issues = PreferMatchFileName().check(unit); RuleTestHelper.verifyNoIssues(issues); }); }); }

В результате, мы получили готовое правило линтера, которое проверяет название файла и класса внутри него и сообщает об ошибке, если они отличаются.
Мы рассмотрели инструмент для анализа кода и поговорили о том, насколько полезно его использование. Я постарался поделиться своими знаниями в создании нового правила. Это был мой первый contribute, для меня это был очень интересный опыт. Как оказалось, вносить изменения в open source совсем несложно. Вот так, потратив пару часов, мы автоматизировали такую проверку для своего проекта, а также для всех тех, кто захочет подключить такое правило к своим проектам, используя Dart Code Metrics.
ссылка на оригинал статьи https://habr.com/ru/articles/592131/
Добавить комментарий