Slide 1

Slide 1 text

Денис Аникин https://xfenix.ru Базовый кодовый стиль хорошего python бекенда

Slide 2

Slide 2 text

Я опять придумал хорошее название

Slide 3

Slide 3 text

Денис Аникин 3 Что я делаю — работаю в Райфе — teamlead в 3 командах — community lead в Python Community — fullstack: typescript, python, devops — много раз выступал на конференциях и митапах https://xfenix.ru

Slide 4

Slide 4 text

Немного слов в начале 4 —Я расскажу о том какой codestyle сейчас у нас (а мы работаем над ним постоянно, поэтому он может быть полезен) в community —Расскажу о том каким он был ранее —На основе этого расскажу как вам самостоятельно писать код по качеству лучше, чем в большинстве проектов, что существуют вокруг, как защититься от типовых ошибок —Даже если вы уже работаете в крупной организации, некоторые решения могут быть для вас новыми

Slide 5

Slide 5 text

Этот доклад местами немного ДУШный

Slide 6

Slide 6 text

Что я понимаю под codestyle? 6 Это может быть не очевидно —Стандартные соглашения по тому как писать код —Стандартные способы предовтращения появления ошибок —Стандартные способы улучшения вашего кода —Стандартные инструменты, помогающие всего этого достигать —Типовые настройки для всего вашего рабочего процесса (от локальной среды до CI/CD конвейера) —И не только

Slide 7

Slide 7 text

Зачем нам нужен codestyle — Снижение когнитивной нагрузки — Ускорение восприятия информации — Как следствие, возможность проще и легче поддерживать (делать новые части и не позволять старым «гнить») ваши проекты — Унификация (многие вещи понятны, даже если не вы их писали) — Устранение многих ошибок ещё до момента их возникновения

Slide 8

Slide 8 text

Итак, сказочка! 8

Slide 9

Slide 9 text

Знакомьтесь! 9

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Трицератослав. Древнерусский тимлид 11

Slide 12

Slide 12 text

А это его команда — 12

Slide 13

Slide 13 text

Junior у него свой меч в спине застрял Middle у него всё норм Senior именно он сжег деревню

Slide 14

Slide 14 text

Они любят жечь деревни 14

Slide 15

Slide 15 text

Как же с ними делать продукт? 15

Slide 16

Slide 16 text

Договориться о правилах — code style, иначе… 16

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

А если не выйдет? 18

Slide 19

Slide 19 text

Трицератослав тимлид, сейчас микроменеджер

Slide 20

Slide 20 text

Вы знаете рецепт — мы просто берем линтеры и размещяем их в пайплайнах 20

Slide 21

Slide 21 text

И вот теперь поговорим о том, что и как размещать 21

Slide 22

Slide 22 text

black 22

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

PEP8 и не только 24 Вместе c black —Стандартный кодовый стиль для питон проектов —Разработчики часто путают PEP и PEP8 —Имеет смысл использовать его как стандарт по-умолчанию и не искать ничего лучшего

Slide 25

Slide 25 text

Многие его не помнят. Я считаю, что это ошибка и PEP8 стоит заучить 25

Slide 26

Slide 26 text

Black 26 Если вы его еще не используете —Имеет мало опций настройки и это правильно (я не очень поддерживаю grey) —Можно конфигурировать длину линии, у нас стандарт 120 —Проверяет, что код продуцирует валидное AST —За более чем 4 года работы в качестве автоматического инструмента у очень большого количества людей показал себя как действительно надежный инструмент

Slide 27

Slide 27 text

А ещё вы можете сказать: our code is blacked 27

Slide 28

Slide 28 text

isort 28

Slide 29

Slide 29 text

И вновь PEP8 29 https://peps.python.org/pep-0008/#imports —Импорты надо разбивать на 3 группы: встроенные, сторонние библиотеки и свой код —Разделять стоит 1 пустой строкой —После этого блока 2 пустых строки

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Открытие 1: большинство нарушает это правило 31

Slide 32

Slide 32 text

Открытие 2: isort по-умолчанию настроен просто ужасно! 32

Slide 33

Slide 33 text

Типовая конфигурация isort 33 Ну и где здесь pep8? —FUTURE —STDLIB —THIRDPARTY —FIRSTPARTY —LOCALFOLDER

Slide 34

Slide 34 text

isort default 34 from __future__ import absolute_import import os import sys from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14, lib15) from my_lib import Object, Object2, Object3 print("Hey") print("yo")

Slide 35

Slide 35 text

isort default — нормальные импорты 35 from __future__ import absolute_import import os import sys from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14, lib15) from my_lib import Object, Object2, Object3 print("Hey") print("yo")

Slide 36

Slide 36 text

isort default — а вот так вообще хорошо 36 from __future__ import absolute_import import os import sys import third_party import my_lib print("Hey") print("yo")

Slide 37

Slide 37 text

Дополнительные соглашения по импортам 37 —Импортируйте встроенные модули целиком (особенно typing) —Если у вас 2 и более импортов из модуля — импортируйте его целиком —Стандартный pep8, но все таки: не импортируйте «звездочку»

Slide 38

Slide 38 text

Конфигурация isort pyproject.toml 38 Для соблюдения PEP8 [tool.isort] line_length = 120 multi_line_output = 3 include_trailing_comma = true lines_after_imports = 2 lexicographical = true sections = ["FUTURE", "STDLIB", "FIRSTPARTY", "THIRDPARTY", "LOCALFOLDER"] no_lines_before = ["STDLIB", "THIRDPARTY"] known_third_party = [] known_local_folder = []

Slide 39

Slide 39 text

docformatter 39

Slide 40

Slide 40 text

Оказывается, одного PEP8 недостаточно. Нужен ещё PEP257 40

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

У этого инструмента есть режим inplace исправления кода 43

Slide 44

Slide 44 text

autoflake 44

Slide 45

Slide 45 text

autoflake 45 —Удаляет неиспользуемые переменные —Удаляет мертвые импорты —Тоже умеет inplace

Slide 46

Slide 46 text

Если настроить автоматом, у вас никогда не будет мертвого кода 46

Slide 47

Slide 47 text

pyupgrade 47

Slide 48

Slide 48 text

Что представляет из себя pyupgrade 48 —Умеет переписывать код под новые версии python —Все так же умеет inplace —В постоянную работу, все же, взять его проблематично (если у вас больше 1 проекта)

Slide 49

Slide 49 text

Я зову эту штуку «вечно свежий python» 49

Slide 50

Slide 50 text

pybetter 50

Slide 51

Slide 51 text

Код ещё лучше 51 —Внедряет некоторые хорошие практики написания кода (их всего 10, но они важные) —И тоже умеет inplace —Увы, слабо развивается

Slide 52

Slide 52 text

52 Пример 1

Slide 53

Slide 53 text

53 Пример 2

Slide 54

Slide 54 text

eradicate 54

Slide 55

Slide 55 text

Все тоже 55 —Берет комментарий, eval’ит его и если тот eval’ится, то удаляет такой комментарий —Тоже умеет inplace

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

Всё просто: при наличии git нет необходимости захламлять код 58

Slide 59

Slide 59 text

Last but not least: Pylint 59

Slide 60

Slide 60 text

Битва pylint vs flake8 60 Плюсы —На мой взгляд pylint содержит нереальное количество хороших практик и советов из коробки —Этот инструмент сразу даёт возможность писать как минимум хорошо —Для тех, кто устал от пачек линтеров, решение «всё в одном»

Slide 61

Slide 61 text

Битва pylint vs flake8 61 А вот дальше идут минусы —Нереально медленный —Очень сложная конфигурация —Fix режима нет (а так можно было?)

Slide 62

Slide 62 text

Картинка по запросу «конфигурация pylint»

Slide 63

Slide 63 text

Аннотации типов 63

Slide 64

Slide 64 text

Лучший тайп чекер — mypy 64 Альтернатива ему — pyright —Сейчас, в 2023 году, имеет смысл включать strict по-умолчанию —Не забывайте о mypy . --install-types —Для pydantic можно подключить комплектный плагин —У моего коллеги, Миши Гурбанова, вышел доклад с подробным исследованием текущих тайпчекеров: https://pycon.ru/da-kto-takie-ehti-vashi- tajp-chekery

Slide 65

Slide 65 text

Свежайшие решения (2023 год) 65

Slide 66

Slide 66 text

Мы выкинули все линтеры! 66

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

…и взяли Ruff 68

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

70 Ruff из Байкала помогает древним разработчикам писать хороший код!

Slide 71

Slide 71 text

71 Деревни больше не горят!

Slide 72

Slide 72 text

Что заменил собой ruff? 72

Slide 73

Slide 73 text

Это впечатляет 73 У вас есть почти всё, что вы вообще могли бы хотеть. И это на космической скорости —pylint —isort —около 30 плагинов flake8 —perflint (устраняет некоторые анти-паттерны, убивающие производительность) —refurb (но частично) —pyupgrade —eradicate —pydocstyle —numpy, airflow, pandas-vet, flynt, mccabe, pep8-naming, pycodstyle —свои правила

Slide 74

Slide 74 text

А теперь кое-что очень важное 74

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

Ruff исправляет. Ошибки. За вас! 77

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

Но если только вы включите все правила по-умолчанию 79

Slide 80

Slide 80 text

А теперь небольшой прикол 80 —Ruff недавно представил ruff formatter —Drop-in replacement для black —НО, есть отличия https://docs.astral.sh/ruff/formatter/black/ —НО (2), есть несовместимые правила линтера https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules

Slide 81

Slide 81 text

Да, они решили заменить black… 81

Slide 82

Slide 82 text

Как ruff сконфигурировать? 82

Slide 83

Slide 83 text

Пример CLI 83 ruff check .\ --ignore=I,EM,FBT,TRY003,S101,D1,FA,ANN101\ --line-length=120\ --fixable=ALL\ --select=ALL \ --unsafe-fixes

Slide 84

Slide 84 text

Пример CLI 84 ruff check .\ --ignore=I,EM,FBT,TRY003,S101,D1,FA,ANN101\ --line-length=120\ --fixable=ALL\ --select=ALL \ --unsafe-fixes

Slide 85

Slide 85 text

Пример CLI с black 85 Да, это не совсем пакетное предложение ruff format .

Slide 86

Slide 86 text

Таким образом, мы делаем ваш код потрясающим автоматически 86

Slide 87

Slide 87 text

Некоторые правила мы все же отключаем 87 Лишь небольшую часть —I (возможно) так как передать конфигурацию глобально невозможно. Если вы индвидуально запускаете ruff в каждом проекте, то можно использовать pyproject.toml и отказаться ещё и от isort! —TRY003 писать message в exception это нормально —D1 можно пропускать все докстринги —ANN101 можно отключать, если вы не хотите везде подписывать typing.Self, так как он нормально выводится mypy сейчас автоматически —EM если вы любите добавлять сообщения в ваши эксепшены —FBT довольно спорный набор правил, запрещающий bool аргументы —(помним) про несовместимую простыню исключений для format https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules

Slide 88

Slide 88 text

Как pyproject.toml может выглядеть 88 Для конфигурации ruff (в бонус пресет для тестов) [tool.ruff] fix = true line-length = 120 select = ["ALL"] ignore = ["D1", "D203", "D213", "FA102", "I”, “ANN001”] [tool.ruff.extend-per-file-ignores] "tests/*.py" = ["S101", "S311"]

Slide 89

Slide 89 text

Как pyproject.toml может выглядеть 89 Для конфигурации ruff (в бонус пресет для тестов) [tool.ruff] fix = true unsafe-fixes = true line-length = 120 select = ["ALL"] ignore = ["D1", "D203", "D213", "FA102", "ANN101"] cache-dir = "/tmp/ruff-cache/" [tool.ruff.isort] no-lines-before = ["standard-library", "local-folder"] known-third-party = [] known-local-folder = ["whole_app"] [tool.ruff.extend-per-file-ignores] "tests/*.py" = ["ANN401", "S101", "S311"]

Slide 90

Slide 90 text

Некоторые правила для тестов 90 Лишь небольшую часть —S101 (вы наверняка хотите ассерты в тестах) —S311 (random в тестах — приемлимо) —ANN401 (иногда хочется any)

Slide 91

Slide 91 text

И это не всё 91

Slide 92

Slide 92 text

Скорость! 92 И это не обман

Slide 93

Slide 93 text

Другие интересные нюансы 93 —Более 600700 правил и число растет быстро —Есть кеширование —Дружит с монорепами

Slide 94

Slide 94 text

Еще не перешли на ruff? 94

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

Я уже ставлю RUFF!

Slide 97

Slide 97 text

Поставил pylint случайно :(

Slide 98

Slide 98 text

Документация 98

Slide 99

Slide 99 text

99 Показал подруге объем документации на проекте

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

Я раскрыл заговор 102

Slide 103

Slide 103 text

No content

Slide 104

Slide 104 text

Документация 104 —Все считают, что она должна быть —Почти у всех её либо нет, либо она не актуальная (что хуже её отсутствия) —Всем всегда стыдно, что её нет и, почему-то, менее стыдно, что она не актуальная —Нет хороших подходов, есть спорные —Автодокументация не работает, её почти не читают —Спеки выглядят хорошо в начале дороги, после сотой в них не разобраться

Slide 105

Slide 105 text

Может быть, нам не писать документацию? 105

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

Не переключайте канал! 107

Slide 108

Slide 108 text

Я предлагаю вам самодокументируемый код 108

Slide 109

Slide 109 text

No content

Slide 110

Slide 110 text

Основные принципы 110 Они довольно простые —Вербозные названия всего: переменных, классов, функций, констант —Не используйте слово get в большинстве случаев —Меньше магических методов (лучше вообще без них) —Никаких чисел кроме 0 и 1 в коде, все остальное — только константы с человеческими именами —Используйте enum для константных перечислений —Функция должна именоваться глаголом, а не существительным. Она что-то делает! —Зен питона — не набор правил

Slide 111

Slide 111 text

Полу-автоматический кодинг 111

Slide 112

Slide 112 text

Принцип 1: все что может быть сделано автоматом, должно быть сделано автоматом 112

Slide 113

Slide 113 text

Как этого достичь? 113 —Вам нужен vscode —Вам понадобятся стандартные плагины mypy и ruff (black и isort, считай, мы уже выселяем)

Slide 114

Slide 114 text

114 Как выглядит конфигурация? "python.linting.mypyEnabled": true, "python.formatting.provider": "black", "python.formatting.blackArgs": [ "--line-length=120", ], "[python]": { "editor.formatOnPaste": true, "editor.formatOnType": true, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": true, }, }, "ruff.args": [ "--line-length=120", "--ignore=I,EM,FBT,TRY003,S101,D101,D102,D103,D104,D105,G004,D107,FA102", "--fixable=ALL", "--select=ALL" ], "emeraldwalk.runonsave": { "commands": [ { "match": ".py", "cmd": "isort --line-length 120 --lines-after-imports 2 --no-lines-before stdlib,localfolder ${file}", "isAsync": true, } ] }

Slide 115

Slide 115 text

115 Как выглядит конфигурация? "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnPaste": true, "editor.formatOnType": true, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": true, "source.organizeImports": true } },

Slide 116

Slide 116 text

Принцип 2: как можно больше AI утилит 116

Slide 117

Slide 117 text

Некоторые полезные решения 117 —Sourcery (правил меньше, чем у ruff, но несколько раз он находил что-то дополнительно). Пока пилотируем. Правил около 200 —Tabnine —Или Code whisperer —Copilot пока лучше всех, но не работает без VPN и аккаунта за 100$ —Можно использовать ChatGPT 3.5, Bard, GigaChat, TheB.AI

Slide 118

Slide 118 text

Аннотации типов 118

Slide 119

Slide 119 text

Как стартануть? 119 Я знаю, может быть не просто —Берете ruff —Берете mypy —Включите режим strict в mypy —Включаете все правила в ruff —Они вас заставят ☺

Slide 120

Slide 120 text

Стремитесь к покрытию кода в 100% и strict в mypy Аннотации tips & tricks 120 —Освойте не только стандартные типы, но и Literal и особенно Final —Final имеет смысл аннотировать все переменные вообще по-умолчанию (чаще всего изменение переменных и ведет к ошибкам) —Примитивные типы можно не аннотировать (a = 5 не надо расписывать как a: int = 5) —Дженерики —Annotated помогает в валидации —сast, overload, final, never, noreturn, typealias, newtype, classvar, typeguard, unpack, Protocol, assert_type, assert_never, TYPE_CHECKING — полезные инструменты

Slide 121

Slide 121 text

121 Пример с Final: до some_value = 5 some_another_value = 'kek’ for one_element in range(100): some_another_value = some_another_value * some_value some_value += 1

Slide 122

Slide 122 text

122 Пример с Final: после import typing some_value: typing.Final = 5 # <- тут само выведет typing.Final[typing.Literal[5]] some_another_value: typing.Final = 'kek' for one_element in range(100): some_another_value = some_another_value * some_value # <- тайпчекер поймает some_value += 1

Slide 123

Slide 123 text

123 TYPE_CHECKING

Slide 124

Slide 124 text

124 Как победить coverage [tool.coverage.report] exclude_also = [ "if typing.TYPE_CHECKING", ]

Slide 125

Slide 125 text

Тонкое эстетическое восприятие отдельных людей 125

Slide 126

Slide 126 text

Мне не нравится как форматирует black… 126

Slide 127

Slide 127 text

5 групп импортов в isort — достаточно логичны… 127

Slide 128

Slide 128 text

Я многое погашу в ruff/pylint, потому что не согласен… 128

Slide 129

Slide 129 text

No content

Slide 130

Slide 130 text

Легко намекну вам — 130

Slide 131

Slide 131 text

No content

Slide 132

Slide 132 text

Серьезные советы 132 Правда (тм) —Успокойте свое чувство эстетики —Общий кодовый стиль и подход стоят гораздо больше, чем «красивые» кавычки или более веселые переносы —Если гасите правила в линтерах, то обязательно записывайте в глобальные командные установки почему вы это делаете —Ваши аргументы должны быть адекватными («мне не понравилось» — не такой аргумент)

Slide 133

Slide 133 text

Дополнительно 133

Slide 134

Slide 134 text

Набор стандартных советов 134 —Всегда «сужайте» try-except блок до минимального количества строк (если возможно, до 1) —Всегда «сужайте» класс ошибки в except (не все знают, но это крайне полезная графическая иллюстрация стандартной иерархии ошибок https://docs.python.org/3/library/exceptions.html#exception-hierarchy) —Делайте ретраи, ограничивайте количество раз, делайте умный интервал и jitter (библиотека backoff поможет) —Храните всю конфигурацию линтеров в pyproject.toml —Избегайте вложенных и сложных условий с помощью инверсии —Используйте композицию и меньше наследования

Slide 135

Slide 135 text

И кое-что ещё 135 —https://github.com/regebro/pyroma позволяет оценить «дружелюбность» вашего пакета —https://github.com/jendrikseipp/vulture ищет мертвый код по всему проекту —https://github.com/dropbox/pyannotate выводит аннотации для вашего кода (но работа довольно ручная) —https://github.com/JelleZijlstra/autotyping добавляет типовые аннотации (не идеально) —https://github.com/bndr/pycycle обнаруживает циклические импорты (тесты?) —https://github.com/Instagram/Fixit любопытный фреймворк для автоисправлений кода (!) —https://github.com/lyz-code/autoimport сам добавляет нужные импорты —https://github.com/pyupio/safety проверяет безопасность проектов —https://github.com/fpgmaas/deptry ищет неиспользуемые, пропущенные, транзитивные зависимости

Slide 136

Slide 136 text

Что по поводу SOLID & GRASP 136 —https://github.com/mschwager/cohesion проверяет связанность вашего кода —Mypy проверяет принцип Liskov substitution principle —Если вы хотите достичь DIP принципа, попробуйте паттерны IoC и DI: на pycon 2023 были посвященные этому доклады. Так же есть неплохие DI фреймворки, мне нравится dependency injector по своему, так же можно посмотреть di, rodi, lagom —Для некоторых других архитектурных линтеров советую доклад Коли https://pycon.ru/arhitektura-knuta-i-pryanika

Slide 137

Slide 137 text

No content

Slide 138

Slide 138 text

No content

Slide 139

Slide 139 text

Теперь качества кода станет значительно лучше 139

Slide 140

Slide 140 text

No content

Slide 141

Slide 141 text

Денис Аникин https://xfenix.ru https://github.com/xfenix/ Вопросы? Поставьте лайк на гитхабе, пожалуйста