12 KiB
Правила разработки
1. Назначение документа
Этот документ фиксирует, как именно должен разрабатываться проект:
- в каком стиле писать код;
- как применять
TDD; - как вести git;
- как принимать архитектурные решения во время реализации.
Цель документа - сделать процесс разработки предсказуемым и не дать проекту расползтись по качеству.
2. Базовые принципы
- сначала проектирование, потом код;
- сначала тест, потом реализация;
- идти по циклу
RGR + commit; - сначала маленький модуль, потом интеграция;
- одна ответственность на один модуль;
- никаких "временных" god-struct и "потом распилим".
3. Основной процесс разработки
Рекомендуемый цикл для каждой фичи:
- Зафиксировать контракт в документации или тесте.
- Написать failing test.
- Реализовать минимальный код, который проходит тест.
- Выполнить refactor без изменения поведения.
- Сделать атомарный commit.
- Если логически завершена часть фичи, сделать push.
- Добавить integration test, если фича выходит за границы одного модуля.
- Обновить документацию, если изменился контракт.
Это и есть базовый TDD-процесс проекта в форме Red -> Green -> Refactor -> Commit.
4. RGR + commit
Для проекта принимается классический цикл:
RedGreenRefactorCommit
Правила:
- без commit после завершенного
RGR-цикла шаг не считается завершенным; - commit должен фиксировать одну логическую единицу изменения;
- если несколько
RGR + commitлогично закрывают часть фичи, после этого делается push; - не нужно ждать полного завершения всей feature branch, чтобы отправить изменения в удаленный репозиторий.
5. TDD-правила
5.1. Что пишется через TDD обязательно
crank-schemacrank-mappingcrank-registrycrank-runtime- YAML import/export
- versioning logic
5.2. Что допускается делать сначала каркасом, потом тестами
- frontend layout;
- wiring приложений;
- пустые
axumhandlers; - начальный scaffold
cargo workspace.
Но как только появляется логика, она должна переходить под тесты.
5.3. Правило минимального шага
Нельзя писать сразу большую "умную" реализацию на сотни строк без промежуточных тестов.
Особенно это запрещено для:
- mapping engine;
- protobuf normalization;
- publish flow;
- YAML import pipeline.
6. Правила по коду
6.1. Стиль модулей
- модуль должен иметь одну четкую ответственность;
- публичный API модуля должен быть минимальным;
- если модуль начинает решать две разные задачи, он делится;
utils,common,helpersдопускаются только в очень редких случаях и с узким смыслом.
6.2. Стиль структур
- маленькие
struct; - явные типы вместо "универсальных" JSON-объектов там, где контракт уже известен;
- protocol-specific поля не смешиваются в одной структуре без discriminated union;
- методы на
implне должны тащить инфраструктурные зависимости.
6.3. Самоописывающийся код
Для проекта принимается подход self-documenting code:
- названия функций, переменных, типов и модулей должны быть достаточно точными, чтобы код читался как текст;
- комментарии в коде считаются исключением, а не нормой;
- если код хочется "объяснить" комментарием, сначала нужно попытаться упростить названия и декомпозицию;
- комментарии в коде по умолчанию не пишутся.
Допустимое исключение:
- редкий комментарий для неочевидного инварианта или ограничения внешнего протокола.
Но базовое правило проекта:
- комментарии в коде исключаем.
6.4. Язык кода и git
Для проекта фиксируется:
- commit messages только на английском языке;
- комментарии в коде только на английском языке, если они все-таки неизбежны;
- имена типов, функций, переменных, модулей и тестов только на английском языке.
6.5. Стиль ошибок
- использовать типизированные ошибки;
- ошибка должна сохранять стадию отказа: schema, mapping, adapter, external, persistence;
- нельзя сваливать все в строковый
anyhowна границах домена.
6.6. Стиль async
asyncиспользовать только там, где есть реальная I/O или async boundaries;- не превращать чистую доменную логику в
asyncбез причины; - не смешивать чистую валидацию и сетевые вызовы в одном методе.
7. Правила проектирования
Проект разрабатывается с опорой на Clean Architecture.
7.1. Что нельзя делать
- один большой
OperationServiceна весь проект; - один
AppStateсо всеми зависимостями мира; - один adapter с
match protocolна сотни строк; - доменные методы, которые знают про SQL, HTTP и файлы одновременно;
- скрытую магию в mapping generation.
7.2. Что нужно делать
- использовать
traitна инфраструктурных границах; - использовать
service/use caseдля orchestration; - держать доменные типы отдельно от DTO;
- создавать explicit runtime view для исполнения;
- держать published flow отдельно от draft editing.
7.3. Dependency rule
Для проекта фиксируется dependency rule:
- внешние слои могут зависеть от внутренних;
- внутренние слои не зависят от внешних;
- домен не знает про HTTP, SQL, storage и transport;
- adapters и repositories реализуют контракты, заданные ближе к домену.
8. Git workflow
8.1. Remote
Удаленный репозиторий проекта:
git@github.com:bsodfather/crank.git
8.2. Ветки
Фиксируем такой workflow:
main- стабильная ветка;- каждая фича делается в отдельной ветке
feat/<feature-name>. - после merge feature-ветка удаляется и в
origin, и локально.
Примеры:
feat/workspace-scaffoldfeat/schema-modelfeat/mapping-enginefeat/admin-api-v1
8.3. Коммиты
Коммит должен:
- быть маленьким;
- содержать одну логическую единицу;
- по возможности включать тесты вместе с реализацией;
- не смешивать refactor и новую фичу без причины.
Коммиты пишутся только на английском языке.
Хороший порядок:
test: add failing tests for schema validationfeat: implement schema validatorrefactor: simplify schema field traversal
8.4. Push policy
Фиксируем такую политику push:
- push делается периодически;
- не нужно ждать полного закрытия feature branch;
- push делается после одного или нескольких логически связанных
RGR + commit; - push должен оставлять ветку в консистентном состоянии.
8.5. Merge cleanup
После merge в main обязательно:
- удалить remote branch;
- удалить локальную branch;
- не оставлять merged feature branches висеть в репозитории.
9. Definition of Done
Фича считается законченной, если:
- код реализован;
- unit/integration tests добавлены и проходят;
- документация обновлена, если изменился контракт;
- нет явного архитектурного долга "починим потом";
- фича вписывается в принятые границы слоев.
При этом для каждой конкретной фичи должен существовать свой локальный DoD, описанный в плане реализации.
10. Порядок принятия решений
Если в ходе реализации возникает спорное решение:
- Проверяется текущая документация.
- Если решение уже зафиксировано, следуем ему.
- Если решение не зафиксировано, сначала обновляется документация.
- Только после этого пишется код.
Это важно, чтобы код не начал определять архитектуру задним числом.
11. Практический итог
Для этого проекта правильный режим разработки такой:
- проектируем заранее;
- пишем через
TDDв формеRGR + commitтам, где есть логика; - двигаемся маленькими этапами;
- ведем каждую фичу в отдельной ветке
feat/<feature-name>; - пушим атомарно и периодически;
- пишем commit messages и неизбежные code comments только на английском;
- стараемся вообще обходиться без комментариев в коде за счет самоописывающегося дизайна;
- не допускаем временных архитектурных компромиссов, которые потом невозможно разгрести.