9.7 KiB
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никогда не попадают в репозиторий.