В разработке программ есть свой ритм.

Сначала главное — чтобы всё заработало. Потом хочется, чтобы интерфейс выглядел достойно. Следом начинается доводка: нужно вычистить ошибки, подтянуть производительность, разобраться с крайними случаями, настроить мониторинг. Когда приложение состоит из пары экранов, обо всём этом обычно не думаешь.

Затем приходит самый непростой этап: сделать так, чтобы спустя полгода в коде смог разобраться человек, который не держит устройство всей системы в голове. Раньше до этого этапа у меня часто просто не доходили руки.

Продукт уже работает, им пользуются, новые функции постепенно появляются. Возникает логичный вопрос: зачем тратить время на документацию, архитектурные правила, тесты и рефакторинг, если заказчик этого почти не видит?

Но проходит время, и простая правка, которая раньше занимала пару часов, растягивается на два дня. Ещё через год на неё может уйти неделя. Обычно дело не в том, что код превратился в кошмар. Просто вокруг него накопился огромный пласт знаний, которые остались в чьей-то голове и нигде не были зафиксированы.

Появляются обычные вопросы, на которые нет ответа в документации:

  • зачем нужны два почти одинаковых сервиса;
  • какой из них считать основным;
  • почему это поле обязательно;
  • можно ли менять этот endpoint;
  • почему нельзя перезапускать процесс;
  • что сломается, если убрать странную проверку.

Когда-то кто-то всё это понимал. Иногда этим человеком был я сам.

Сложность — это не количество файлов

Раньше я думал, что управлять сложностью — значит построить правильную архитектуру: грамотно разделить систему на модули, не плодить лишние зависимости, использовать понятные шаблоны, выносить общее, писать тесты.

Всё это действительно важно. Но со временем мой взгляд изменился.

Большая система всё равно останется большой. Если в ней несколько приложений, база данных, очередь задач, десяток сервисов, внешние API, фоновые процессы и разные окружения, никакой рефакторинг не превратит её в простой калькулятор.

Поэтому я больше не пытаюсь сделать сложные системы простыми. Я стараюсь сделать их понятными.

Это не одно и то же.

Простота зависит от масштаба задачи. Понятность — от того, насколько хорошо зафиксированы устройство системы, принятые решения и способы проверки.

Пишу инструкцию для следующего разработчика

Только теперь этим следующим разработчиком всё чаще становится AI.

Для каждого серьёзного проекта я готовлю краткий документ — карту системы. Не толстую документацию на сотни страниц, а рабочий ориентир:

  • что я строю;
  • из каких частей состоит система;
  • где живёт бизнес-логика;
  • что считается главным источником данных;
  • как данные проходят через систему;
  • какие существуют окружения;
  • как устроен релиз;
  • какие решения уже приняты;
  • какие части лучше не менять, пока не разберёшься в последствиях.

Раньше такой документ один раз читал новый сотрудник. Потом карта устаревала и постепенно превращалась в археологическую находку.

С AI ситуация оказалась интереснее. Агент действительно читает карту перед каждой задачей. Документация перестала быть формальностью, которую пишут «потому что положено». Она стала частью рабочего процесса.

Сначала агент получает контекст, затем изучает код.

Разница кажется небольшой, но на практике она огромна. Без контекста AI быстро придумывает собственную версию архитектуры. Эта версия может быть вполне логичной, аккуратной и даже красивой. Проблема в том, что она не совпадает с системой, которая уже работает.

Хорошие и плохие примеры

Если однажды в проекте удалось грамотно решить типовую задачу — сделать API-контроллер, воркер, компонент, работу с очередью или интеграцию с внешним сервисом, — нет смысла заставлять следующего агента снова изобретать решение.

Я сохраняю такие реализации как эталонные примеры. Перед созданием нового компонента агент сначала смотрит, как подобные компоненты принято делать в этом проекте. Он не должен слепо копировать код. Важнее понять подход: структуру, границы ответственности, обработку ошибок и способ проверки.

Рядом постепенно появилась обратная коллекция: примеры того, как делать не стоит. Она оказалась даже полезнее.

Например, ответ языковой модели нельзя сразу отдавать пользователю. Модель может вернуть служебный текст, неожиданную разметку, некорректные данные или убедительно сформулированную ошибку.

Можно один раз исправить такой баг и забыть о нём. А можно сохранить правило:

Результат LLM — это недоверенный ввод. Перед использованием его нужно проверить.

Тогда ошибка оставляет после себя не только исправленный код, но и новое знание.

Мне нравится мысль, что каждый хорошо разобранный баг должен немного снижать вероятность следующего. Иначе я буду бесконечно исправлять одну и ту же ошибку в разных местах.

Один факт — одно место

Есть простой способ незаметно нарастить сложность: скопировать один список в четыре разных файла.

Через несколько месяцев эти списки неизбежно начнут отличаться. То же самое происходит с типами данных, конфигурациями, правилами, URL, промптами, статусами и бизнес-ограничениями.

Поэтому я стараюсь находить для каждого такого факта одно главное место.

Если есть список сервисов, остальные конфигурации должны строиться на его основе. Если определён общий контракт данных, приложения должны использовать его, а не создавать собственные похожие версии. Если существует системный промпт, не нужно хранить ещё одну его копию прямо в коде.

Здесь нет сложной архитектурной теории. Если факт записан только в одном месте, невозможно забыть обновить его копию в другом — потому что копии нет.

Самая опасная фраза — «заодно»

AI охотно помогает. Просишь его исправить одну ошибку, а он замечает ещё три потенциальные проблемы, устаревшую библиотеку и дублирование логики. Иногда заодно предлагает перестроить половину архитектуры.

Нередко он прав. Но исходная задача всё равно заключалась в том, чтобы исправить одну ошибку.

Поэтому я стал гораздо строже задавать границы работы. Перед заметным изменением агент должен понимать:

  • что именно нужно изменить;
  • что не входит в задачу;
  • какие части системы могут быть затронуты;
  • как выглядит готовый результат;
  • чем этот результат будет проверен;
  • какие действия могут привести к неприятным последствиям.

Особенно важно явно перечислять то, чего делать не нужно. Большинство неприятностей в разработке начинается с безобидной фразы: «А давайте заодно…»

Я больше не верю фразе «всё работает»

Это относится не только к AI.

Разработчик пишет код, проверяет его и говорит: «Работает». Но что именно он проверил? Какой результат получил? Соответствует ли это реальному пользовательскому сценарию?

Теперь для каждого изменения я стараюсь заранее построить понятный путь проверки.

Если исправляю баг, сначала по возможности воспроизвожу его тестом. Тест падает. Я вношу исправление. Тест проходит. Затем проверяю модуль целиком и сборку. Если изменение затрагивает соседние части системы, проверяю и их. Для критичных функций иду дальше и прохожу настоящий пользовательский сценарий.

Количество тестов само по себе ничего не доказывает. Важно, чтобы доказательство соответствовало утверждению.

Зелёный unit-тест не гарантирует, что функция работает в production. Статус процесса online не означает, что пользователь может выполнить нужное действие. Фраза AI «задача полностью выполнена» — это уверенное мнение, а не доказательство.

Если я утверждаю, что функция работает, у меня должен быть результат проверки именно этой функции.

Документация стала результатом работы

Здесь AI заметно изменил экономику процесса.

Раньше после большой задачи приходилось выбирать: двигаться дальше или остановиться и подробно описать сделанное. Нетрудно догадаться, какой вариант чаще побеждал.

Теперь значительную часть этой работы можно поручить агенту:

  • изменился архитектурный контракт — обновить его описание;
  • нашлась повторяющаяся ошибка — добавить её в список антипаттернов;
  • появился новый сервис — дополнить карту проекта;
  • изменился пользовательский сценарий — проверить связанные тесты и документацию;
  • найден удачный способ решения — сохранить его как пример.

Конечно, за документацией тоже нужно следить. AI способен уверенно описать собственные догадки как существующее устройство системы. Поэтому код, конфигурация и работающий runtime остаются главными источниками фактов. Документ должен сверяться с ними, а не подменять их.

Но поддерживать документацию стало настолько дешевле, что уже странно этого не делать.

У меня получился повторяющийся цикл:

задача → код → проверка → новое знание → документация → контекст для следующей задачи

Проект накапливает не только код. Он накапливает опыт работы с этим кодом.

С чего начать

Для такого подхода необязательно сразу создавать сложную систему правил. В качестве отправной точки можно дать агенту базовый промпт:

Промпт для агента
Перед тем как вносить изменения, разберись, как устроен проект:

1. Прочитай основной документ с описанием проекта.
2. Найди архитектурные правила, эталонные решения, известные ошибки и антипаттерны, относящиеся к задаче.
3. Не доверяй документации безоговорочно. Проверь реальный код, схемы, API и существующие тесты.

Перед реализацией зафиксируй:

- что нужно изменить;
- что точно не входит в задачу;
- какие части системы будут затронуты;
- как будет выглядеть готовый результат;
- как ты будешь проверять результат;
- какие побочные эффекты возможны.

Не расширяй задачу «заодно».

Если исправляешь баг или добавляешь новое поведение, по возможности сначала напиши тест, который воспроизводит требуемое изменение.

Не создавай дубликаты типов, конфигураций, списков и бизнес-правил, если в проекте уже есть единый источник истины.

Проводи проверки от узких к широким:

1. Целевой тест.
2. Тесты затронутой части.
3. Сборка и проверка типов.
4. Зависимые проверки.
5. Полный набор проверок, если он действительно нужен.

Не пиши «всё проверено», если какие-то проверки не выполнялись. Перечисли, что было проверено на самом деле.

Если в ходе работы ты обнаружил новое архитектурное правило, повторяющуюся ошибку или важную особенность проекта, предложи обновление документации.

Не выполняй опасные действия — deploy, сброс базы, изменение инфраструктуры или перезапуск production — без отдельной явной команды.

В конце сообщи:

1. Что было изменено.
2. Какие контракты затронуты.
3. Что проверено.
4. Что обновлено в документации.
5. Какие риски или нерешённые вопросы остались.

Сам по себе этот промпт не сотворит чудес. Если в проекте нет нормального контекста, тестов и понятных правил, агенту не на что будет опереться.

Но с чего-то нужно начинать.

Четвёртый этап я больше не откладываю

Раньше я сначала запускал программу, затем дорабатывал интерфейс и стабилизировал систему. О документации, архитектурных правилах и накопленных знаниях вспоминал, когда проект уже начинал трещать под собственной тяжестью. Если на это оставались время и бюджет.

Теперь четвёртый этап можно выполнять постепенно, одновременно с основной работой.

Написал код — оставил тест. Принял решение — зафиксировал его. Наступил на грабли — положил их в каталог и подписал. Нашёл хороший способ решить типовую задачу — сохранил его как эталон.

Следующий AI-агент приходит уже не в пустое поле. Он получает опыт предыдущих задач: карту системы, принятые решения, рабочие примеры, известные ошибки и способы проверки.

Сложность никуда не исчезла. Большая система осталась большой системой. Но всё меньше её частей держится только в моей памяти.

И этого уже достаточно, чтобы работать с ней увереннее.


Если в вашем проекте слишком многое держится в головах отдельных людей, я могу помочь собрать карту системы, определить источники истины и настроить работу AI-агентов так, чтобы они начинали с контекста и заканчивали проверяемым результатом. Расскажите мне о своей задаче.