Когда монолит не лезет в контекстное окно
Вы когда-нибудь пытались попросить GPT-5 или Claude 4 Opus написать весь проект за один промпт? Даже с токенными окнами под 200K, в 2026 году это всё ещё ад блока проекта. Только представьте: у вас микросервис на 150 КБ исходного кода — логика, валидация, работа с внешним API. Вы скармливаете это нейросети, просите добавить новую фичу, а в ответ — гигантский скрипт, где половина — просто забытый старый код, а другая — галлюцинации о том, чего в проекте никогда не было. Знакомо?
Проблема не в моделях, а в их ограничениях. Даже самые умные LLM не могут удержать в голове 100+ КБ кода без потери логики, если не использовать специальные архитектурные трюки.
Обычный совет — натягивать RAG: разбить код на чанки, загнать в векторную базу, настроить ретривер. Но RAG — это оверхед, который не всегда оправдан. Особенно когда проект на стадии прототипа или вайб-кодинга — когда слепая вера в ИИ даёт быстрый результат. Но как только код переваливает за 100 КБ, вера заканчивается, а начинается борьба за читаемость и адекватность.
Почему контекст — враг больших проектов
Давайте честно: даже 200K токенов — это не так много, как кажется. Промпт с описанием фичи, парой примеров кода и всей базой проекта — и вот уже 50% окна занято. LLM начинает забывать предыдущие инструкции, переписывать модули, которые уже были, и генерировать мусор. В статье про антипаттерны AI-кода мы разбирали, как 6 МБ мусора появляется из-за слепого доверия к структуре. Здесь то же самое — модель пытается скопировать шаблон из обучения, не понимая, что у вас уже есть другой код.
Главная причина — отсутствие грануляции. Если модель видит весь проект как один огромный контекст, она не может выделить чёткие границы между модулями. Ей проще родить ещё один файл, чем разбираться, что уже написано. А если вы пошлёте ей больше одного файла — начнётся путаница, какой из них главный.
Альтернатива RAG: стратегия дробления
Вместо того чтобы тащить инфраструктуру для ретривала, можно сделать код самодостаточным. Идея простая: разбить проект на такие блоки, каждый из которых помещается в один промпт, а связь между ними — только через чёткие интерфейсы. Тогда ИИ может работать с каждым блоком отдельно, не зная, что творится в соседнем. Это похоже на микросервисы, но внутри одного репозитория, без лишних HTTP-запросов.
Такая стратегия решает несколько проблем:
- Контекстная изоляция — модель не перегружается лишними деталями.
- Повторяемость — изменив один блок, вы не сломаете другой, если интерфейс остался неизменным.
- Тестируемость — каждый блок можно тестировать в изоляции, а значит, контролировать качество генерации.
В 2026 году, когда модели вроде GPT-5 стали умнее, но всё ещё страдают от деградации контекста при длинных сессиях, дробление — буквально спасательный круг.
1Разделяй и властвуй: грануляция по функциям
Не надо дробить по слоям (контроллеры, сервисы, репозитории) — это приведёт к тому, что один модуль всё равно будет держать половину проекта. Дробите по логическим единицам функциональности. Например, в парсере данных: DataLoader, DataValidator, DataTransformer, ReportBuilder. Каждый из них — отдельный файл с единственной публичной функцией или классом.
Как это выглядит в коде:
# data_loader.py
def load_csv(file_path: str) -> list[dict]:
"""Загружает CSV и возвращает список словарей."""
pass # реализация
# data_validator.py
def validate(records: list[dict], schema: dict) -> list[str]:
"""Проверяет записи по схеме. Возвращает список ошибок."""
pass
# report_builder.py
def build_summary(records: list[dict], errors: list[str]) -> str:
"""Строит текстовый отчёт на основе записей и ошибок."""
passКаждый файл — до 100 строк. Идеально для одного промпта: «Допиши функции в data_loader.py по такой-то спецификации».
2Жёсткие интерфейсы как контракты с LLM
ИИ склонен менять сигнатуры функций, если ему дать волю. Поэтому каждый модуль должен иметь фиксированный публичный API. В Python это Protocol или абстрактные классы. В Go — интерфейсы. В TypeScript — типы. Зафиксируйте их отдельным файлом interfaces.py или api_definitions.ts, на который вы будете ссылаться в каждом промпте.
Пример для Python с дата-классами:
# interfaces.py
from dataclasses import dataclass
from typing import Protocol
@dataclass
class Record:
name: str
value: float
tags: list[str]
class DataSaver(Protocol):
def save(self, records: list[Record]) -> None: ...
class FilterStrategy(Protocol):
def filter(self, records: list[Record]) -> list[Record]: ...Теперь, когда вы просите ИИ реализовать CsvSaver, вы скармливаете ему interfaces.py и свой файл-заглушку. Модель не может изменить типы — она просто реализует контракт.
Важно: не давайте LLM менять файл с интерфейсами. Пусть он будет read-only контекстом. Иначе модель «оптимизирует» его, объединяя типы, и всё сломается.
3Тесты как спецификация для одноразовых промптов
Самый недооценённый приём: сначала пишите unit-тесты для модуля (руками или с помощью ИИ), а потом просите LLM реализовать код, проходящий тесты. Это и контракт, и верификация, и мотивация не трогать соседние модули. Заодно решается проблема профессиональных практик AI-кодинга.
Пример:
# test_data_loader.py
from data_loader import load_csv
def test_load_csv_returns_list():
result = load_csv("test.csv")
assert isinstance(result, list)
def test_load_csv_skips_header():
result = load_csv("test.csv")
assert all(isinstance(r, dict) for r in result)Теперь промпт: «Реализуй load_csv, чтобы проходили тесты из test_data_loader.py. Импорт из interfaces уже есть». Такой промпт редко галлюцинирует.
4Сборка: склейка блоков в главный модуль
Когда все блоки реализованы, остаётся только собрать их вместе. Главный файл main.py или app.py должен быть минимальным — только импорты и вызовы. Его тоже можно генерировать отдельно, давая ИИ список доступных модулей и описание бизнес-логики.
Пример main.py:
from data_loader import load_csv
from data_validator import validate
from report_builder import build_summary
records = load_csv("input.csv")
errors = validate(records, SCHEMA)
report = build_summary(records, errors)
print(report)Если интерфейсы соблюдены, этот код будет работать без изменений, даже если вы переписывали load_csv три раза разными промптами.
Где стратегия ломается: типичные ошибки дробления
- Мелкая грануляция до абсурда. Когда каждый блок — 5 строк, вы утопаете в интерфейсах и тестах, а промпты становятся однотипными. Золотая середина — один модуль на одну нетривиальную функцию (10-50 строк кода).
- Глобальное состояние. Если модули используют один глобальный объект (база данных, конфиг), они перестают быть изолированными. Решение — передавать зависимости явно (Dependency Injection).
- Изменение интерфейсов по ходу. Решили, что
load_csvдолжен возвращать не список словарей, а генератор? Вы обновилиinterfaces.py— и сломали все реализованные модули. Приходится переписывать. Поэтому интерфейсы меняйте редко и осознанно. - Игнорирование типов. В Python без типов ИИ может возвращать что угодно. Используйте mypy или Pydantic — модель станет аккуратнее.
Забавно, но в статье про провал ИИ в генерации кода описан ровно тот же антипаттерн: нейросеть спроектировала чип, не разбивая его на модули, и получила размером с участок. Дробление спасает от таких катастроф.
Когда дробление не нужно (и стоит собрать RAG)
Стратегия дробления хороша, когда проект пишется с нуля или эволюционирует под контролем человека. Но если у вас легаси-монолит на 500 КБ, который вы не хотите переписывать, и вы просто хотите, чтобы ИИ помог найти баг — тогда RAG с чанками по 500 строк и семантическим поиском будет быстрее. Дробление требует архитектурной дисциплины с самого начала. Если вы уже в аду из 30 файлов с циклическими импортами, сначала приведите проект в порядок, а потом уже дробите.
Также RAG незаменим, когда кода много (миллионы строк), и его нереально разбить на независимые блоки — они связаны неявно. Но для 95% стартапов и pet-проектов дробление — это дешёвая альтернатива, которая работает.
Неочевидный совет: дробление как тест архитектуры
Если вам кажется, что какой-то модуль сложно разбить на блоки для ИИ — пересмотрите его дизайн. Разбиение на промпт-френдли блоки заставляет вас думать о связности и ответственности. В каком-то смысле, вы не просто генерируете код — вы улучшаете архитектуру, даже если ИИ пишет всё за вас. А когда проект дорастёт до продакшена, у вас уже будет чистая архитектура с модульными тестами.
Попробуйте этот подход на следующем проекте. Начните с interfaces.py и тестов для одной функции. Увидите, как исчезнут галлюцинации, а вайб-кодинг превратится в предсказуемую сборку из кубиков Лего.