Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Как прокачать линтер
Search
Maxim Mazaev
March 23, 2019
Programming
0
33
Как прокачать линтер
Python Meetup Chelyabinsk #5
Maxim Mazaev
March 23, 2019
Tweet
Share
More Decks by Maxim Mazaev
See All by Maxim Mazaev
Проверка типов в большом проекте
mmazaev
0
23
Pylint изнутри. Как он это делает
mmazaev
0
79
Как поддерживать согласованность в микросервисной архитектуре
mmazaev
0
53
Other Decks in Programming
See All in Programming
GoのGenericsによるslice操作との付き合い方
syumai
3
700
Systèmes distribués, pour le meilleur et pour le pire - BreizhCamp 2025 - Conférence
slecache
0
120
A2A プロトコルを試してみる
azukiazusa1
2
1.3k
Rubyでやりたい駆動開発 / Ruby driven development
chobishiba
1
520
GitHub Copilot and GitHub Codespaces Hands-on
ymd65536
1
130
エンジニア向け採用ピッチ資料
inusan
0
180
#kanrk08 / 公開版 PicoRubyとマイコンでの自作トレーニング計測装置を用いたワークアウトの理想と現実
bash0c7
1
660
スタートアップの急成長を支えるプラットフォームエンジニアリングと組織戦略
sutochin26
0
170
AIコーディング道場勉強会#2 君(エンジニア)たちはどう生きるか
misakiotb
1
270
WindowInsetsだってテストしたい
ryunen344
1
210
都市をデータで見るってこういうこと PLATEAU属性情報入門
nokonoko1203
1
580
技術同人誌をMCP Serverにしてみた
74th
1
450
Featured
See All Featured
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
8
680
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
5
230
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Build The Right Thing And Hit Your Dates
maggiecrowley
36
2.8k
Music & Morning Musume
bryan
46
6.6k
Making the Leap to Tech Lead
cromwellryan
134
9.4k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
30
2.1k
How GitHub (no longer) Works
holman
314
140k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
3.1k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.3k
jQuery: Nuts, Bolts and Bling
dougneiner
63
7.8k
Balancing Empowerment & Direction
lara
1
390
Transcript
Как прокачать линтер Максим Мазаев
• Программист в ЦИАН • Пишу на Python • Спикер
PyCon, Moscow Python Conf++ О себе
Как прокачать линтер
• 1 млн уникальных пользователей в сутки • 10x разработка
за 2014-2019 • 100+ разработчиков • 30 питонистов • Сотни строк нового кода каждый день О ЦИАН
Код-ревью в Циане 1. Одобрение от робота (тесты, линтер etc.)
2. Одобрение от 2 разработчиков
Проблемы с код-ревью • Есть внутренние договоренности по стилю кода.
Линтер не покрывает
Проблемы с код-ревью • Есть внутренние договоренности по стилю кода.
Линтер не покрывает • Линтер не покрывает наши договоренности по стилю.
Проблемы с код-ревью Программист работает вместо линтера
Решение • Инструмент который знает про наши соглашения • И
проверяет их
Решение
Решение ?
Решение А не написать ли нам свой линтер?
Решение А не написать ли нам свой линтер? Нет, у
нас уже есть pylint • он удобный • он встроен в наши процессы
Решение Написать плагин для Pylint
Пример №1
2015 Sergey # TODO: выпилить (не выпилили) 2015 Misha #
TODO: Убрать после успешного релиза (не убрали) 2017 Vadim # TODO: после эксперимента удалим (не удалили) ¯\_(ツ)_/¯
Пример №1 Проблема: В коде много комментариев «TODO» • Висят
годами • Не выполняются • Копится техдолг
class TodoIssueChecker(BaseChecker): __implements__ = IRawChecker Будем итерироваться по строкам модуля
(«сырой» чекер) Решение. Пишем свой чекер для Pylint
msgs = { 'С9999': ('Комментарий с TODO без ссылки на
задачу', 'issue-code-in-todo', 'Длинное описание')} Код и мнемоничеcкое название чекера Пишем свой чекер
С1234 [C]onvention [W]arning Произвольные 4 цифры [E]rror (уникальные в рамках
pylint) [F]atal [R]efactoring Пишем свой чекер. Код сообщения
msgs = { 'С9999': ('Комментарий с TODO без ссылки на
задачу', 'issue-code-in-todo', 'Длинное описание')} # pylint: disable=C9999 # pylint: disable=issue-code-in-todo Пишем свой чекер
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 ) Пишем свой чекер Выводим предупреждение Ходим по строкам
def register(linter: PyLinter) -> None: linter.register_checker( TodoIssueChecker(linter) ) * Модуль
чекера должен быть в PYTHONPATH Регистрируем чекер *
$ cat work.py # TODO: Удалю через неделю, честно-честно! $
pylint work.py --load-plugins todo_checker ... И запускаем!
Тем временем внутри pylint...
1. Инициализация плагинов 1. Импортируем все модули, где есть плагины
2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
2. Разбираем пул чекеров Чекер Чекер Чекер Чекер Чекер Чекер
Raw checker AST checker Token checker
3. Запуск чекера Запускаем метод process_module, определенный в чекере checker.process_module(module)
$ cat work.py # TODO: Удалю через неделю, честно-честно! $
pylint work.py --load-plugins todo_checker C: 0, 0: Комментарий с TODO без ссылки на задачу (issue-code-in-todo) Результат
get_offer_by_params( “sale”, True, 859483, ) Пример №2. Keyword-arguments Не всегда
очевидно что это за аргументы
get_offer_by_params( “sale”, True, 859483, ) Пример №2. Keyword-arguments get_offer_by_params( deal_type=”sale”,
truncate=True, cian_id=859483, ) Плохо Хорошо
Пример №2. Keyword-arguments Проблема: Сложно написать «сырой» checker
Пример №2. Keyword-arguments Проблема: Сложно написать «сырой» checker Решение: Чекер
на основе AST
Лирическое отступление про AST
Преобразование кода в 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')) ]))]
Задача. • Найти все ноды Call (вызов функции) • Посчитать
количество аргументов • Проверить отсутствие позиционных аргументов Call( func=Name(name='get_offer'), args=[Const(value=1298880)], keywords=[ ... ]))]
class NonKeywordArgsChecker(BaseChecker): __implements__ =IAstroidChecker Чекер на основе AST Решение. Пишем
чекер на основе AST
msgs = { 'С9191': ('Краткое описание', 'keyword-only-args', 'Длинное описание')} Код
и мнемоничеcкое название чекера Пишем чекер на основе AST
def visit_call(self, node: Call) … visit_<Имя_ноды> вызывается при заходе на
ноду leave_<Имя_ноды> при уходе с ноды Пишем чекер на основе AST
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
def register(linter: PyLinter): linter.register_checker( TodoIssueChecker(linter) ) Регистрируем чекер
$ cat work.py get_offers(1, True, deal_type=”sale”) $ pylint work.py --load-plugins
non_kwargs_checker ... И запускаем!
Тем временем внутри pylint...
1. Инициализация плагинов 1. Импортируем все модули, где есть плагины
2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
2. Разбор модуля на AST Разбор выполняет библиотека Astroid (обертка
над AST-парсером) github.com/PyCQA/astroid/
Почему Astroid, а не AST (stdlib) 1. Разбирает дерево с
помощью typed_ast (c поддержкой тайпхинтов из PEP 484)
Почему Astroid, а не AST (stdlib) from module import Entity
def foo(bar): # type: (Entity) -> None return
Почему Astroid, а не AST (stdlib) 2. Собирает обратно с
дополнительными плюшками: from foo import * Импортирует и добавит в locals содержимое foo
Почему Astroid, а не AST (stdlib) 3. Transform plugins •
Поможем Astroid разобраться в динамической природе питона. • Дополняем AST полезной информацией см. pylint-django
3. Разбираем пул чекеров Чекер Чекер Чекер Чекер Чекер Чекер
AST checker Raw checker Token checker
4. Разобрать чекеры по типам нод 1. У каждого чекера
находим методы visit_<Имя ноды> leave_<Имя ноды>
4. Разобрать чекеры по типам нод 2. И сохраняем: _visit_methods
= dict( <Имя ноды>: [checker1, checker2,… ] ) _leave_methods = dict( <Имя ноды>: [checker1, checker2,… ] )
$ 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) Запускаем чекер!
class TestNonKwArgsChecker(CheckerTestCase): CHECKER_CLASS = NonKeywordArgsChecker Чекер, который тестируем A тесты
написать?
1. Создаем тестовую AST-ноду node = astroid.extract_node( "get_offers(3, 'magic', 'args')"
) А тесты написать?
2. Проверяем что Pylint бросает предупреждение with self.assertAddsMessages(message): self.checker.visit_call(node) А
тесты написать?
Еще один тип чекера - TokenChecker Под капотом - tokenize,
лексический сканер
Лексический анализ кода NAME NAME OP NAME OP NAME OP
OP NEWLINE def sum(a, b): INDENT NAME NAME OP NAME NEWLINE return a + b DEDENT ENDMARKER
Как Pylint работает с TokenChecker 1. Токенизирует модуль 2. Для
всех чекеров с интерфейсом ITokenChecker вызывает: process_tokens(tokens)
Применение TokenChecker 1. Проверка правописания 2. Проверка отступов 3. Работа
со строками. ...
Хочу все знать github.com/PyCQA/Pylint
Выводы Чего добились: • Переложили рутинные проверки на линтер •
Уменьшили количество отклоненных пулл-реквестов • Повысили качество ревью
Максим Мазаев
[email protected]
facebook.com/mazaev.maksim