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

Moscow Python Meetup №86. Денис Аникин (Райффайзен банк, Community Lead). Базовый кодовый стиль хорошего Python-бэкенда

Moscow Python Meetup №86. Денис Аникин (Райффайзен банк, Community Lead). Базовый кодовый стиль хорошего Python-бэкенда

Расскажу о том как эволюционировало представление о кодовом стиле у меня и у нашего внутрибанковского комьюнити и к чему мы пришли. Покажу свод правил, конфигурацию и опции настройки для того, чтобы относительно расслабленно систематически достигать очень хорошего качества исходного кода.

Здесь не будет срыва покровов, взрывных правил, неожиданных утилит, крутых разработок, скорее собранное вместе руководство. Пригодится тем, кто не заморачивался на кодстайл; тем, кого не устраивает их кодстайл; тем, кого по какой-то причине не устроили существующие гайды.

Видео: https://youtu.be/G3JKtB8tgvg

Moscow Python: http://moscowpython.ru
Курсы Learn Python: http://learn.python.ru
Moscow Python Podcast: http://podcast.python.ru
Заявки на доклады: https://bit.ly/mp-speaker

Moscow Python Meetup

November 22, 2023
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. Денис Аникин 3 Что я делаю — работаю в Райфе

    — teamlead в 3 командах — community lead в Python Community — fullstack: typescript, python, devops — много раз выступал на конференциях и митапах https://xfenix.ru
  2. Немного слов в начале 4 —Я расскажу о том какой

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

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

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

    него всё норм Senior именно он сжег деревню
  6. PEP8 и не только 24 Вместе c black —Стандартный кодовый

    стиль для питон проектов —Разработчики часто путают PEP и PEP8 —Имеет смысл использовать его как стандарт по-умолчанию и не искать ничего лучшего
  7. Black 26 Если вы его еще не используете —Имеет мало

    опций настройки и это правильно (я не очень поддерживаю grey) —Можно конфигурировать длину линии, у нас стандарт 120 —Проверяет, что код продуцирует валидное AST —За более чем 4 года работы в качестве автоматического инструмента у очень большого количества людей показал себя как действительно надежный инструмент
  8. И вновь PEP8 29 https://peps.python.org/pep-0008/#imports —Импорты надо разбивать на 3

    группы: встроенные, сторонние библиотеки и свой код —Разделять стоит 1 пустой строкой —После этого блока 2 пустых строки
  9. 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")
  10. 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")
  11. isort default — а вот так вообще хорошо 36 from

    __future__ import absolute_import import os import sys import third_party import my_lib print("Hey") print("yo")
  12. Дополнительные соглашения по импортам 37 —Импортируйте встроенные модули целиком (особенно

    typing) —Если у вас 2 и более импортов из модуля — импортируйте его целиком —Стандартный pep8, но все таки: не импортируйте «звездочку»
  13. Конфигурация 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 = []
  14. Что представляет из себя pyupgrade 48 —Умеет переписывать код под

    новые версии python —Все так же умеет inplace —В постоянную работу, все же, взять его проблематично (если у вас больше 1 проекта)
  15. Код ещё лучше 51 —Внедряет некоторые хорошие практики написания кода

    (их всего 10, но они важные) —И тоже умеет inplace —Увы, слабо развивается
  16. Все тоже 55 —Берет комментарий, eval’ит его и если тот

    eval’ится, то удаляет такой комментарий —Тоже умеет inplace
  17. Битва pylint vs flake8 60 Плюсы —На мой взгляд pylint

    содержит нереальное количество хороших практик и советов из коробки —Этот инструмент сразу даёт возможность писать как минимум хорошо —Для тех, кто устал от пачек линтеров, решение «всё в одном»
  18. Битва pylint vs flake8 61 А вот дальше идут минусы

    —Нереально медленный —Очень сложная конфигурация —Fix режима нет (а так можно было?)
  19. Лучший тайп чекер — mypy 64 Альтернатива ему — pyright

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

    вообще могли бы хотеть. И это на космической скорости —pylint —isort —около 30 плагинов flake8 —perflint (устраняет некоторые анти-паттерны, убивающие производительность) —refurb (но частично) —pyupgrade —eradicate —pydocstyle —numpy, airflow, pandas-vet, flynt, mccabe, pep8-naming, pycodstyle —свои правила
  21. А теперь небольшой прикол 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
  22. Некоторые правила мы все же отключаем 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
  23. Как 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"]
  24. Как 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"]
  25. Некоторые правила для тестов 90 Лишь небольшую часть —S101 (вы

    наверняка хотите ассерты в тестах) —S311 (random в тестах — приемлимо) —ANN401 (иногда хочется any)
  26. Другие интересные нюансы 93 —Более 600700 правил и число растет

    быстро —Есть кеширование —Дружит с монорепами
  27. Документация 104 —Все считают, что она должна быть —Почти у

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

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

    плагины mypy и ruff (black и isort, считай, мы уже выселяем)
  30. 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, } ] }
  31. 115 Как выглядит конфигурация? "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnPaste": true,

    "editor.formatOnType": true, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": true, "source.organizeImports": true } },
  32. Некоторые полезные решения 117 —Sourcery (правил меньше, чем у ruff,

    но несколько раз он находил что-то дополнительно). Пока пилотируем. Правил около 200 —Tabnine —Или Code whisperer —Copilot пока лучше всех, но не работает без VPN и аккаунта за 100$ —Можно использовать ChatGPT 3.5, Bard, GigaChat, TheB.AI
  33. Как стартануть? 119 Я знаю, может быть не просто —Берете

    ruff —Берете mypy —Включите режим strict в mypy —Включаете все правила в ruff —Они вас заставят ☺
  34. Стремитесь к покрытию кода в 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 — полезные инструменты
  35. 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
  36. 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
  37. Серьезные советы 132 Правда (тм) —Успокойте свое чувство эстетики —Общий

    кодовый стиль и подход стоят гораздо больше, чем «красивые» кавычки или более веселые переносы —Если гасите правила в линтерах, то обязательно записывайте в глобальные командные установки почему вы это делаете —Ваши аргументы должны быть адекватными («мне не понравилось» — не такой аргумент)
  38. Набор стандартных советов 134 —Всегда «сужайте» try-except блок до минимального

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