--- 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: ```toml # 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. Комментарии Лучший комментарий это тот, который не понадобился, потому что код и так понятен. Пишу комментарий, когда нужно объяснить **почему**, а не **что**: ```python # 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` никогда не попадают в репозиторий.