Ревью границ модулей
Где сейчас протекает абстракция, где модули знают слишком много друг о друге.
Ревью границ модулей в {{codebase}}.
Цель: понять что плохо разделено, какие модули знают слишком много друг о друге, и где это приводит к проблемам.
1. Карта зависимостей
Построй граф: модуль A импортирует модуль B.
Инструменты:
madge --image graph.svg src/dependency-cruiser- Любой dep-graph визуализатор
Смотри:
- Кто на ком завязан
- Циклические зависимости (плохо!)
- "Звёзды" — модули от которых зависит всё
- Сироты — модули которые ни от кого не зависят
2. Признаки плохих границ
A. Циклические зависимости
auth ──→ user
↑ ↓
└─── permissions
Эти модули нельзя поменять независимо. Слей или вынеси общий interface.
B. "Бог-модуль"
Один модуль импортится 50+ файлами и сам импортит 30+. Слишком много ответственности.
C. Прямое обращение к internals
// плохо — лезем в кишки
import { _internal_state } from './user/internals';
// хорошо — через публичный API
import { getCurrentUser } from './user';
D. Утечки типов
Если user.ts экспортит тип Database internals — модуль user знает про БД. Зачем?
E. Сквозные ID
Если order.userId, а в user.ts тоже order.userId — кто owner? Должна быть одна сторона.
3. Bounded contexts
Группируй модули в bounded contexts (как в DDD):
[Identity]
- auth
- user
- session
[Billing]
- subscription
- invoice
- payment
[Catalog]
- product
- category
- inventory
Правила
- Внутри context — могут зависеть друг от друга
- Между context'ами — только через явный interface
- ID — реплицируются, не разделяются (Billing.userId ≠ Identity.User, это просто ссылка)
4. Direction of dependencies
Зависимости должны идти в одну сторону. Снизу вверх:
high-level (use cases)
↓
mid-level (domain)
↓
low-level (infrastructure)
НЕ наоборот: domain не должен импортить ничего из infrastructure.
Для разворота направления — Dependency Inversion (интерфейс в верхнем слое, реализация в нижнем).
5. Тесты-проба
Хорошие границы дают:
- ✓ Можешь юнит-тестить модуль без моков соседей
- ✓ Можешь заменить модуль на in-memory fake
- ✓ Можешь удалить модуль и понять что сломается из списка импортеров
Плохие границы:
- ✗ Чтобы протестировать A, нужно стартануть B, C, D
- ✗ Изменение в A ломает 20 файлов
6. Action items
| Проблема | Где | Почему плохо | Что делать |
|---|---|---|---|
| Циклическая зависимость auth ↔ user | src/auth, src/user | Нельзя менять независимо | Вынести User в /domain, auth и user импортят оттуда |
| Direct import internals | src/billing/index.ts:15 | Хрупко при рефакторе user | Использовать публичный getCurrentUser() |
7. Эволюция
Не пытайся сразу выправить всё. Приоритизируй:
- P0: циклы (блокируют рефакторинг)
- P1: прямые обращения к internals (хрупко)
- P2: дублирование типов
- P3: именования модулей
Один PR — одна граница. Не "пройдёмся по всему" — сломаешь всё.
Принципы
- Хорошая граница — её не нужно объяснять
- "А что если этот модуль выкинуть" — если ответ "ничего" или "одна вещь сломается" — граница чистая
- Лучше явный дубль чем неявная зависимость
Multi-agent: координатор и специалисты
Архитектура из координатора и специализированных агентов: передача контекста, дедупликация, race conditions.
Новый subagent или новый skill: что выбрать
Decision tree: создавать ли отдельного агента или достаточно skill. Критерии — контекст, переиспользование, frequency, complexity.
Выделение модуля из большого файла
Найти зону ответственности, вынести в отдельный модуль с чётким API.