Files
site/content/code-style.md
2026-10-09 16:21:55 +04:00

9.7 KiB
Raw Permalink Blame History

title, date
title date
Стандарты разработки ПО 2026-10-08T19:18:00+04:00

Это правила, по которым я пишу код. В основе лежит книга Роберта Мартина "Чистый код" и PEP 8 для Python. Документ живой, я дополняю его по мере того, как набиваю шишки.

Главная мысль одна: код читают намного чаще, чем пишут. Поэтому я пишу его для человека, который откроет файл через полгода. Часто этот человек я сам.

0. Без ТЗ результат ХЗ

Любая работа начинается с требований, а не с кода.

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

1. Стиль кода: PEP 8 + мои правила

PEP 8 это базовый стандарт. Мои правила работают поверх него, как каскад в CSS: все, что я не переопределил, наследуется от PEP 8 как есть. Переопределение одно.

Табуляция вместо пробелов. Один уровень вложенности это один символ. Каждый видит отступ той ширины, которую выставил у себя в редакторе, а в файле при этом ничего не меняется. Пробелы и табы в одном файле не смешиваю никогда, Python 3 это и не позволит.

Все остальное по PEP 8: длина строки, пустые строки между функциями и классами, порядок импортов, пробелы вокруг операторов.

Чтобы не держать это в голове, стиль проверяет и правит инструмент. Я использую ruff:

# pyproject.toml
[tool.ruff]
line-length = 100

[tool.ruff.format]
indent-style = "tab"

[tool.ruff.lint]
select = ["E", "F", "I", "N", "B", "UP"]
ignore = ["W191"]  # W191 ругается на табы, у нас это осознанное решение

IDE: JetBrains (PyCharm). В настройках проекта включены табы и запуск ruff при сохранении.

2. Имена

Имя должно отвечать на три вопроса: зачем это существует, что делает и как используется. Если для имени нужен комментарий, значит имя плохое.

  • Переменные и функции в snake_case, классы в PascalCase, константы в UPPER_CASE.
  • Функции называю глаголом: fetch_page, parse_price, send_report. Классы и переменные существительным: Browser, proxy_pool.
  • Без сокращений и однобуквенных имен. Исключение: i в коротком цикле и e для исключения.
  • Одно понятие, одно слово. Если в проекте есть fetch, то не появляются рядом get, load и retrieve для того же самого.
  • Без кодирования типа в имени: users, а не users_list или lst_users.
  • Булевы переменные читаются как вопрос: is_ready, has_proxy, can_retry.

3. Функции

  • Маленькие. Функцию должно быть видно целиком на экране без прокрутки. Если не видно, ее можно разбить.
  • Делают одно дело. Если функцию нельзя описать одним предложением без слова "и", это две функции.
  • Один уровень абстракции. Функция либо управляет процессом и вызывает другие функции, либо работает с деталями. Не одновременно.
  • Мало аргументов. Идеально ноль-два. Три и больше это повод собрать их в dataclass.
  • Без флагов в аргументах. render(page, True) непонятно читается. Лучше две функции: render_full и render_preview.
  • Без скрытых побочных эффектов. Если функция называется check_session, она не должна по дороге создавать новую сессию.
  • Команда или запрос. Функция либо меняет состояние, либо возвращает данные. Не то и другое сразу.

4. Комментарии

Лучший комментарий это тот, который не понадобился, потому что код и так понятен.

Пишу комментарий, когда нужно объяснить почему, а не что:

# Cloudflare отдает 403 на первый запрос без cookie, поэтому всегда делаем два
response = session.get(url)

Не пишу:

  • комментарии, которые пересказывают код
  • закомментированный код. Для истории есть git
  • журнал изменений и авторство в шапке файла. Это тоже работа git

Docstring обязателен для публичных функций и классов модуля. Для внутренних по ситуации.

5. Обработка ошибок

  • Исключения вместо кодов возврата. Функция не возвращает None или -1, если что-то пошло не так, она бросает исключение.
  • Ловлю конкретные исключения, никогда голый except: или except Exception: pass.
  • Свои исключения для своей предметной области: ProxyBannedError, CaptchaError. По ним понятно, что случилось, без чтения стектрейса.
  • Не возвращаю None там, где ждут коллекцию. Пустой список лучше, чем проверка на None в каждом месте вызова.
  • Ошибку обрабатываю там, где знаю, что с ней делать. Если не знаю, пропускаю выше.

6. Классы и модули

  • Маленькие классы с одной ответственностью. У класса должна быть одна причина для изменения. Если класс и ходит в сеть, и парсит HTML, и пишет в базу, это три класса.
  • Высокая связность. Методы класса работают с его полями. Если метод не трогает self, ему, скорее всего, не место в этом классе.
  • Зависимости снаружи. Класс получает браузер, сессию или клиент базы в конструкторе, а не создает их сам. Так его легко тестировать и подменять.
  • Закон Деметры. Общаюсь только с ближайшими объектами. order.customer.address.city это знак, что нужен метод.
  • Границы с чужим кодом. Сторонние библиотеки оборачиваю в свой тонкий слой. Если библиотеку придется заменить, правка будет в одном месте.

7. Тесты

  • Тесты это тоже код и к ним те же требования к чистоте.
  • Тест проверяет одну вещь, и это видно по его имени: test_returns_empty_list_when_page_has_no_items.
  • Структура каждого теста: подготовка, действие, проверка.
  • Тесты быстрые, независимые друг от друга и дают одинаковый результат при каждом запуске. Никаких походов в реальный интернет в юнит-тестах.
  • Баг сначала воспроизвожу тестом, потом чиню.

8. Правило бойскаута

Оставляю код чище, чем он был до меня. Не нужно переписывать все сразу. Достаточно переименовать одну непонятную переменную или вынести один кусок в функцию каждый раз, когда трогаю файл.

9. Git

  • Один коммит, одно логическое изменение.
  • Сообщение коммита в формате conventional commits: feat:, fix:, refactor:, docs:, chore:.
  • В основную ветку не попадает код, который не проходит линтер и тесты.
  • Секреты, ключи и .env никогда не попадают в репозиторий.