Как я заставил ИИ писать код по книжке: Clean Architecture + TDD на автопилоте

от автора

Для привлечения внимания

Для привлечения внимания

Это продолжение первой статьи про взрослый вайб-код для тех разработчиков, которые уже перешли на тёмную сторону. Ради неё, на самом деле, писалась первая часть: меня очень попросили опубликовать свой ИИ-фреймворк, в котором сейчас производится 99% моего кода, поэтому делюсь тут.


Меня зовут Саша Раковский. Работаю техлидом в расчётном центре одного из крупнейших банков РФ, где ежедневно проводятся миллионы платежей, а ошибка может стоить банку сотни миллионов рублей. Законченный фанат экстремального программирования, а значит и DDDTDD, и вот этого всего. Штуки редкие, крутые, так мало кто умеет — для этого я здесь, делюсь опытом. Если стало интересно, добро пожаловать в мой блог.


В прошлой части я разобрал базу по ИИ-разработке для начинающих и накинул несколько тезисов от себя:

  1. На голых промптах далеко не уедешь. В ИИ-разработке необходим системный подход к решению повторяющихся задач, исправлении повторяющихся ошибок, в управлении контекстом. Без ИИ-фреймворка — никак.

  2. Без автотестов хоть сколько-то адекватная ИИ-разработка невозможна. Именно автотесты должны быть центральным элементом ИИ-разработки. Если софт — это совокупность поведения системы, то зелёный автотест — это единица поведения, фундаментальная частица, из которой построен этот софт.

  3. Автономная ИИ-разработка — это пока фантастика. Даже самые лучшие модели чудят так, что никакими тестами не предугадаешь все их возможные косяки. Человеческое ревью необходимо обязательно.

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

Из опенсорсных TDD-фреймворков мне известен только один — Superpowers. И это вполне годная штука, если вы предпочитаете легковесные подходы в дизайне и автотестах, но при этом хотите поддерживать высокую дисциплину разработки.

Однако, именно поэтому мне, например, superpowers не очень подходит — слишком уж легковесный. Он практически противоречит ATDD и плохо встраивается в более структурированные процессы разработки. В то же время, в процессе разработки у меня как-то само по себе родилось своё решение, заточенное под принятый в нашей команде «книжный подход».

И раз получилось неплохо, то почему бы, по просьбам подписчиков своего канала, и не поделиться прогрессом на текущий момент. Фреймворк проверен на 3,5 месяцах работы, 4к коммитов, 1500 тестов, ~350 — e2e, 25к строк продакшн-кода (12k java back, 12k ts/tsx front). И столько же тест-кода.

Описание

Начнем с концепции. Любой софт — это совокупность поведения системы. Каждая единица поведения должна иметь свой тест-кейс. Зелёный, то есть подтвержденный. Поэтому хороший софт — это совокупность зелёных тест-кейсов. Именно вокруг этой идеи построены процессы разработки в нашей команде. И именно она легла в основу самого фреймворка.

Верхнеуровнево фреймворк проходит следующую последовательность шагов:

  • краткое описание продукта + перечисление историй

  • история 1

    • этап спецификации

      • интервью

      • (проектирование требований на основе интервью и описания продукта)

      • генерация кейсов на основе требований

    • этап реализации сгенерированных кейсов:

      • кейс 1

        • красный тест

        • (реализация)

        • зелёный тест

      • кейс 2

      • … (остальные кейсы)

  • история 2

    • этап спецификации

    • этап реализации

Вся работа разбита на истории — минимально зависимые друг от друга фичи, каждая из которых поставляет свою рбособленную ценность. Как говорил Кент Бек пользакам: «Tell me your story». История должна покрывать какой-то один аспект пользовательского опыта. Например, «однажды я забыл пароль» — вполне себе подобная история, которая превращается в фичу «Сброс пароля».

Разработка истории начинается с постановки требований: описания истории, критериев приёмки, проектирования апи, макетов. Эти требования необходимы для составления списка тест-кейсов — главного артефакта для каждой истории.

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

/continue

Центральным элементом фреймворка является скилл /continue, который, опираясь на текущее состояние проекта — список историй и их прогресс-файлы, определяет, какой дальнейший шаг необходимо сделать.

По ссылке, я специально сделал демо-пример классического бэк+фронт приложения — Kanban-доски с 3 запланированными к разработке историями в stories.md:

  1. Создать таску.

  2. Передвинуть таску в другую колонку.

  3. Изменить/удалить таску.

Пройдёмся по ним в обратном порядке:

  1. К истории #3 я намеренно не приступал, поэтому /continue 3 начнет проектирование с самого первого шага — написания спецификации:

    1. проведёт и запротоколирует интервью;

    2. составит описание истории;

    3. построит макеты интерфейса;

    4. определит используемое апи и спроектирует новое, если надо;

    5. сгенерирует тест-кейсы, необходимые для реализации.

  2. В истории #2 спека уже есть, но разработка еще не начата, поэтому «/continue 2» начнет уже с разработки бэка: найдет первый кейс, прочитает спеку, напишет красный приёмочный тест и побежит шаг за шагом его зеленить в коротких red-green-refactor TDD-циклах, прерываясь на ревью после каждого шага.

  3. В истории #1 уже сделаны все кейсы на бэке и даже начат фронт, поэтому вызов скилла «/continue 1» приведёт к тому, что разработка продолжится с фронтового кейса — с последнего шага, который отмечен в progress.md истории, как выполненный.

Но, как видите, тут мы встречаем первое ограничение: фреймворк был разработан мной для своих задач — разработки серверного приложения. Чтобы использовать фреймворк для мобильного, десктопного приложения, embedded, игр или для еще какого-то софта, может понадобиться доработка фреймворка через специально придуманный для этого скилл /prompt-update.

progress.md

В каждой истории есть свой progress.md, который фиксирует, на чем остановилась работа над историей в прошлый раз. Это длинный чек-лист, разбитый на несколько фаз:

  • спецификация

  • реализация:

    • бэкенд

    • внешние интеграции

    • фронтенд

    • безопасность

    • нагрузка

    • инфраструктура.

Спецификация состоит, как я уже говорил, из нескольких шагов: интервью, описания, макетов, апи и тест-плана.

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

Все остальные фазы состоят из кейсов, сгенерированных, в первой фазе.

TDD-ядро

В свою очередь, каждый кейс разбивается на отдельные TDD-шаги. Тут уже фреймворк очень сильно начинает навязывать свой подход к разработке — а именно ATDD, процесс в котором большой TDD-цикл оборачивает внутри несколько маленьких.

Например, для бэка это выглядит так:

  • красный приёмочный (e2e, acceptance) тест;

    • красный usecase-тест;

    • зелёный usecase-тест;

    • красный адаптер-тест (например, контроллер);

    • зелёный адаптер-тест;

    • … такие же мини-циклы для других адаптеров

  • зелёный приёмочный тест;

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

Архитектурные правила

Как видим, тут навязана Clean Architecture с общепринятым в гексагональной архитектуре ATDD. Этот подход к тестированию описан, например, в «Get Your Hands Dirty on Clean Architecture», Tom Hombergs и «Hexagonal Architecture Explained» от самого автора гексагоналки, Alistair Cockburn. Если вы предпочитаете иную архитектуру или иной тестовый подход, то тут потребуется доработать фреймворк под себя с помощью /prompt-update, о котором я уже упоминал, но подробнее расскажу чуть позже.

Quality gates

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

  • на красном шаге это ревью строгости тестов и соблюдения тестовой архитектуры + рефакторинг;

  • на зелёном шаге это рефакторинг + проверка покрытия тестами.

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

После всех гейтов следует коммит, и в дело вступаем мы, отсматривая diff коммита и высказывая замечания. Если замечаний нет или они устранены, мы сбрасываем контекст и делаем /continue. Фреймворк молотит дальше, а мы идем в другое окно IDE разруливать другой добежавший до конца /continue.

Контекст-менеджмент

Каждый шаг реализации разбит на 2 этапа: написание теста/кода и последующие гейты (ревью, рефакторинг, coverage). Чтобы эти активности друг на друга не влияли, все подшаги запускаются в отдельных агентах.

Другой приём управления контекстом — этот тот самый progress.md, позволяющий сбрасывать контекст после каждого шага, и начинать с пустого контекста, загружая туда только самое необходимое, повышая надежность вывода.

Самоулучшение

Если я сталкиваюсь с каким-то косяком не в первый раз, я вызываю /prompt-update с описанием проблемы. После исправления фреймворка, я отсматриваю изменения, откатываю последний коммит, сбрасываю контекст, и проверяю, что в этот раз проблема решена.

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

Как попробовать

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

  • репа с примером Канбан-доски на Java+React стеке,

  • репа с голым фреймворком.

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

Рекомендуемый сценарий:

  1. Я предполагаю, что у вас есть подписка на клод или на клод-совместимую LLM (например, GLM5 от Z.ai или вы умеете запускать Claude-конфигурацию в Codex).

  2. Склонируйте репу с Канбан доской. Рекомендую склонировать 3 раза и открыть каждую репу в отдельной IDE. Ну или используйте worktrees, если так привычнее.

  3. В каждой из IDE откройте терминал, зайдите в клод, и введите /continue 1, /continue 2, /continue 3 соответственно.

  4. В первой ide клод приступит к написанию selenium-теста для фронтового кейса, на котором остановилась разработка.

  5. Во второй ide начнёт разработку с того места, где остановилась вторая история — с acceptance-теста для первого бэкенд-кейса.

  6. В третьей ide клод увидит, что третья история ещё не начата и приступит к разработке с самого первого шага — интервью.

Про параллелизацию

Почему 3 ide?

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

Это один из недостатков получившегося фреймворка — цикл, на который Claude уходит в работу, очень долгий. Впрочем, если дефицита задач нет, то параллелизация работы позволяет все равно двигаться именно с той скоростью, с которой вы способны ревьюить изменения. Каждая фича будет ехать примерно, как если бы она была написана руками, но одновременно с ней будут ехать еще 3-4 минимум.

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

Почему не worktree?

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

Как у меня

Мой типовой поток состоит из 6 параллельно запущенных IDE, в которых 4-5 молотят /continue или обрабатывают замечания по результатам работы этого скилла, а в оставшихся я занимаюсь какими-то техдолгами: планирую рефакторинги, ищу проблемы в коде, оптимизирую пайп.

Про порты

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

Если папка будет иметь на конце индекс: «continue-example1», «continue-example2», «continue-example3», то это позволит фреймворку забить для каждого репозитория порты так, чтобы при запуске приложений и инфраструктуры разные потоки друг другу не мешали.

Как стартануть с нуля

Альтернативный вариант — пустая репа с голым фреймворком. Я запарился и потратил кучу времени на отладку и тестирование фреймворка, чтобы он, независимо от выбранных технологий, мог доехать от пустой репы, до простенького mvp на одних лишь только вызовах /continue и небольших корректирующих усилиях по причесыванию каких-то заскоков.

Что делать?

  1. Клонируете пустой фреймворк.

  2. Прописываете:

    1. описание продукта в BriefProductDescription.md

    2. ожидаемую нагрузку/производительность в ExpectedLoad.md

    3. технологии в technology.md

    4. генерите вместе с клодом список историй и просите его записать их в stories.md

  3. И дальше /continue без остановки.

У меня отняло несколько вечеров, чтобы отвязать фреймворк от этой своей джавы и сделать его более-менее рабочим на других языках. Я сделал несколько шаблонов под 90% основных технологий и языков. И оно даже работает на всех из них плюс-минус так, как я ожидаю. Но если вы с другого языка, то вы, вероятно, можете испытать некую форму культурного шока от того, как результат не похож на то, что принято в вашем языке. Так что /prompt-update вам в помощь.

Полезные фишки

Помимо /continue и саб-скиллов, которые он оркестрирует, есть еще парочка полезных скиллов, которые вызываются вне основного потока /continue.

/prompt-update

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

Рядом с ним еще живёт /prompt-refactor, который предназначен для поиска проблемных мест во фреймворке. Но этот скилл я вызываю нечасто: порой он предлагает ерунду всякую, поэтому может использоваться только в условиях глубокого понимания работы фреймворка и контекста. Например, чтобы оценить последние изменения: /prompt-refactor last commit.

/task

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

Скилл /continue умеет итерироваться по задачам так же, как и по историям. Таски хранятся в своей папочке и после реализации мигрируют в папочку /done. Я в своей разработке использую его не реже раза в день.

/doc

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

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

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

Портирование и ограничения

У фреймворка есть много ограничений:

  • он был разработан под конкретный технологический стек;

  • под определенные подходы к разработке — Clean Architecture + DDD + OOP + Martin Fowler’s Refactoring Book;

  • под определенный подход к тестированию, ATDD — Acceptance Tests (Continuous Delivery book) + hexagonal architecture testing (adapter tests + application tests);

  • под бэк + фронт веб-приложение;

  • для экономии токенов используется английский язык.

В целом, если от технологии я его как-то отвязать смог, то ни от Clean Architecture, ни от ATDD даже пока не планировал. Поэтому, если Clean Architecture и ATDD для вас чужды, то, возможно, проще будет попытаться под себя адаптировать SuperPowers.

Но, если уж вам понравилось решение, то для редактирования фреймворка под свои задачи, вам потребуется часик-другой доработать его напильником скиллом /prompt-update.

Техника безопасности

Ну и, как обычно, в завершении техника безопасности:

  • ни ИИ, ни фреймворк не заменит разработчика: если на ревью не будет замечен баг, то он уедет в прод и все сломает;

  • фреймворк работает по принципу «железо дешевле людей» и предпочитает сэкономить человеческое время, потратив токены, поэтому мне не всегда даже $200 подписки Claude хватает для 5 часового окна при работе в несколько потоков;

  • фреймворк исполняется ллмкой, ллмки нарушают правила, поэтому иногда фреймворк будет сходить с рельс, и надо его подправлять (самое частое — иногда требуется повторно дёрнуть скилл /refactor или /test-review на чистом контексте);

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

  • фреймворк разработан под вкусовые предпочтения автора, если у вас другие — /prompt-update или superpowers;

  • фреймворк молодой, не учитывает и не может учитывать всего, поэтому он нуждается в постоянной доработке с помощью /prompt-update.

В общем, приятной эксплуатации. За обновлениями — в мой канал.

ссылка на оригинал статьи https://habr.com/ru/articles/1023998/