8.1 KiB
Swift Implementation Patterns Mastermind
Этот документ описывает нормативные паттерны реализации кода на Swift.
1. Concurrency & State Ownership
Problem: Состояние гонки (race conditions) и неопределённое поведение при многопоточности.
Rule: Каждая stateful-возможность (Capability) или сервис принадлежат конкретному actor. Межмодульный обмен — только через Sendable неизменяемые типы (value types). MainActor используется исключительно для UI-проекций.
Apply when: При проектировании хранилищ, сервисов и вью-моделей.
Avoid: Использование lock, DispatchQueue для синхронизации состояния вручную; захват мутабельного состояния в замыканиях.
Trade-offs: Требует понимания Swift Concurrency и использования await.
Verification: Swift 6 Strict Concurrency не должен выдавать предупреждений и ошибок.
2. Bounded Async Streams
Problem: Неконтролируемое накопление событий в очередях (backpressure) приводит к утечкам памяти и задержкам.
Rule: Все AsyncStream должны иметь ограниченный буфер (bounded) и явную политику обработки переполнения (dropOldest, dropNewest или coalesce).
Apply when: Для потоков аудио-фреймов, скриншотов и событий UI.
Avoid: Создание неограниченных потоков событий.
Verification: Каждый стрим должен иметь тесты на поведение при переполнении.
3. Lightweight UDF (Unidirectional Data Flow)
Problem: Сложная двусторонняя синхронизация UI и бизнес-логики.
Rule: Использование однонаправленного потока данных: Immutable State → View → Intent (Action) → Service/Reducer → New State. Без обязательной зависимости от тяжелых фреймворков (TCA).
Apply when: В реализации Companion Island и экранов управления.
Avoid: Прямая мутация состояния из View; использование Binding для бизнес-логики.
Verification: View зависит только от State и отправляет Intents.
4. Boundary State Machines
Problem: Неявные переходы между состояниями (например, Collectors) приводят к трудновоспроизводимым багам.
Rule: Использование явных конечных автоматов (State Machines) для жизненного циклаCollectors, сессий и миграций. Недопустимые переходы должны быть невозможны на уровне типов.
Apply when: Управление жизненным циклом сложных компонентов.
Avoid: Большое количество разрозненных Bool флагов для описания состояния.
Verification: Unit-тесты покрывают матрицу переходов.
5. Validated Value Types
Problem: Проброс примитивов (String, Int) через все слои приводит к потере смысла и ошибкам валидации.
Rule: Использование отдельных типов-обёрток для доменных понятий (ID, Timestamp, Confidence). Проверка инвариантов происходит при создании типа.
Apply when: Все доменные сущности и параметры портов.
Avoid: Использование String для ID или Double для Confidence без обёртки.
Verification: Код компилируется только при передаче правильных типов; невозможны "пустые" или некорректные значения.
6. Manual Composition Root
Problem: Глобальные синглтоны и Service Locator делают зависимости неявными.
Rule: Использование ручного внедрения зависимостей (Constructor Injection) в единственной точке входа (Composition Root). Глобальные мутабельные синглтоны запрещены.
Apply when: Инициализация приложения в AppShell.
Avoid: Использование shared instance для бизнес-логики.
Verification: Все зависимости можно подменить (mock) в тестах без изменения кода модулей.
7. Workflow-Sized Services
Problem: Use cases, которые делают слишком мало (один метод) или слишком много (весь модуль).
Rule: Application Service должен отражать осмысленный пользовательский или системный воркфлоу (например, AssistantSessionService) и оркестровать несколько портов.
Avoid: Создание класса UseCase для каждой мелкой функции; "божественные" объекты-координаторы.
Verification: Сервис покрывает логически связанную группу действий.
8. Typed Failure States
Problem: Обобщённые ошибки Swift.Error не дают понимания, как на них реагировать.
Rule: Ожидаемые ошибки моделируются как типизированные состояния (Enum). Ошибки адаптеров переводятся в доменные ошибки на границе модуля.
Apply when: Возврат результатов из портов и сервисов.
Avoid: Проброс NSError или URLError в доменные слои.
Verification: UI может точно отобразить причину сбоя на основе типа ошибки.
9. Structured Task Ownership
Problem: Утечки задач (detached tasks) и сложности с отменой (cancellation).
Rule: Каждая долгоживущая Task принадлежит владельцу жизненного цикла и отменяется при его завершении. Task.detached запрещён, кроме системных воркеров.
Apply when: Запуск Collectors и фоновой обработки.
Avoid: "Fire-and-forget" задачи без сохранения ссылки на отмену.
Verification: Deinit объекта приводит к остановке всех запущенных им задач.
10. Dedicated Executors for Blocking Work
Problem: Блокировка потока актора или MainActor тяжелыми вычислениями.
Rule: Все блокирующие операции (SQLite, ML, PCM) выносятся на выделенные очереди или исполнители (Dedicated Executors/Queues). Оркестрация акторов не должна выполнять тяжелую работу.
Apply when: I/O, обработка медиа, криптография.
Avoid: Выполнение Data(contentsOf:) или сложных циклов на MainActor.
Verification: Профилирование в Instruments не показывает блокировок UI потока.