Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Максим Мазаев. Как прокачать линтер

Максим Мазаев. Как прокачать линтер

В ЦИАН мы постоянно пользуемся линтером для проверки качества кода. Но случаются моменты, когда хочется проверять код на соответствие внутренним соглашениям, про которые линтер ничего не знает. Разработчикам приходится держать все соглашения в голове и самостоятельно проверять качество кода. Это усложняет и затягивает код-ревью. Я расскажу о том, как мы решили эту проблему с помощью Pylint, который имеет мощную систему создания плагинов. Покажу на примерах как плагины могут упростить процесс код-ревью. Рассмотрим процесс написания плагинов и попутно разберемся как работает сам Pylint изнутри.

More Decks by Python Community Chelyabinsk

Other Decks in Programming

Transcript

  1. • 1 млн уникальных пользователей в сутки • 10x разработка

    за 2014-2019 • 100+ разработчиков • 30 питонистов • Сотни строк нового кода каждый день О ЦИАН
  2. Проблемы с код-ревью • Есть внутренние договоренности по стилю кода.

    Линтер не покрывает • Линтер не покрывает наши договоренности по стилю.
  3. Решение А не написать ли нам свой линтер? Нет, у

    нас уже есть pylint • он удобный • он встроен в наши процессы
  4. 2015 Sergey # TODO: выпилить (не выпилили) 2015 Misha #

    TODO: Убрать после успешного релиза (не убрали) 2017 Vadim # TODO: после эксперимента удалим (не удалили) ¯\_(ツ)_/¯
  5. Пример №1 Проблема: В коде много комментариев «TODO» • Висят

    годами • Не выполняются • Копится техдолг
  6. class TodoIssueChecker(BaseChecker): __implements__ = IRawChecker Будем итерироваться по строкам модуля

    («сырой» чекер) Решение. Пишем свой чекер для Pylint
  7. msgs = { 'С9999': ('Комментарий с TODO без ссылки на

    задачу', 'issue-code-in-todo', 'Длинное описание')} Код и мнемоничеcкое название чекера Пишем свой чекер
  8. С1234 [C]onvention [W]arning Произвольные 4 цифры [E]rror (уникальные в рамках

    pylint) [F]atal [R]efactoring Пишем свой чекер. Код сообщения
  9. msgs = { 'С9999': ('Комментарий с TODO без ссылки на

    задачу', 'issue-code-in-todo', 'Длинное описание')} # pylint: disable=C9999 # pylint: disable=issue-code-in-todo Пишем свой чекер
  10. def process_module(self, node): with node.stream() as stream: for (lineno, line)

    in enumerate(stream): if has_todo_without_issue_code(line): self.add_message( 'issue-code-in-todo', line=lineno ) Пишем свой чекер Выводим предупреждение Ходим по строкам
  11. def register(linter: PyLinter) -> None: linter.register_checker( TodoIssueChecker(linter) ) * Модуль

    чекера должен быть в PYTHONPATH Регистрируем чекер *
  12. $ cat work.py # TODO: Удалю через неделю, честно-честно! $

    pylint work.py --load-plugins todo_checker ... И запускаем!
  13. 1. Инициализация плагинов 1. Импортируем все модули, где есть плагины

    2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
  14. $ cat work.py # TODO: Удалю через неделю, честно-честно! $

    pylint work.py --load-plugins todo_checker C: 0, 0: Комментарий с TODO без ссылки на задачу (issue-code-in-todo) Результат
  15. Преобразование кода в AST. Вызов функции get_offer( 112, deal_type=’sale’, offer_type=’flat’,

    ) Call( func=Name(name='get_offer'), args=[Const(value=112)], keywords=[ Keyword( arg='deal_type', value=Const(value='sale') Keyword( arg='offer_type', value=Const(value='flat')) ]))]
  16. Задача. • Найти все ноды Call (вызов функции) • Посчитать

    количество аргументов • Проверить отсутствие позиционных аргументов Call( func=Name(name='get_offer'), args=[Const(value=1298880)], keywords=[ ... ]))]
  17. msgs = { 'С9191': ('Краткое описание', 'keyword-only-args', 'Длинное описание')} Код

    и мнемоничеcкое название чекера Пишем чекер на основе AST
  18. def visit_call(self, node: Call) … visit_<Имя_ноды> вызывается при заходе на

    ноду leave_<Имя_ноды> при уходе с ноды Пишем чекер на основе AST
  19. def visit_call(self, n: Node): if n.args and len(n.args + n.keywords)

    > 2: self.add_message( 'keyword-only-args', node=node ) Пишем чекер на основе AST
  20. 1. Инициализация плагинов 1. Импортируем все модули, где есть плагины

    2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
  21. Почему Astroid, а не AST (stdlib) 1. Разбирает дерево с

    помощью typed_ast (c поддержкой тайпхинтов из PEP 484)
  22. Почему Astroid, а не AST (stdlib) from module import Entity

    def foo(bar): # type: (Entity) -> None return
  23. Почему Astroid, а не AST (stdlib) 2. Собирает обратно с

    дополнительными плюшками: from foo import * Импортирует и добавит в locals содержимое foo
  24. Почему Astroid, а не AST (stdlib) 3. Transform plugins •

    Поможем Astroid разобраться в динамической природе питона. • Дополняем AST полезной информацией см. pylint-django
  25. 4. Разобрать чекеры по типам нод 1. У каждого чекера

    находим методы visit_<Имя ноды> leave_<Имя ноды>
  26. 4. Разобрать чекеры по типам нод 2. И сохраняем: _visit_methods

    = dict( <Имя ноды>: [checker1, checker2,… ] ) _leave_methods = dict( <Имя ноды>: [checker1, checker2,… ] )
  27. $ cat work.py get_offers(1, True, deal_type=”sale”) $ pylint work.py --load-plugins

    non_kwargs_checker C: 0, 0: Функция с >2 аргументами вызывается с позиционными аргументами (keyword-only-args) Запускаем чекер!
  28. Лексический анализ кода NAME NAME OP NAME OP NAME OP

    OP NEWLINE def sum(a, b): INDENT NAME NAME OP NAME NEWLINE return a + b DEDENT ENDMARKER
  29. Как Pylint работает с TokenChecker 1. Токенизирует модуль 2. Для

    всех чекеров с интерфейсом ITokenChecker вызывает: process_tokens(tokens)
  30. Выводы Чего добились: • Переложили рутинные проверки на линтер •

    Уменьшили количество отклоненных пулл-реквестов • Повысили качество ревью