ИИ все прочнее входит в работу программиста. Кто-то все еще отрицает его роль, кто-то с энтузиазмом пробует все новые возможности, но квалифицированное большинство все же трезво замечает, что ИИ пока не годится для сложных проектов, хотя простые задачи уже выполняет неплохо.
Расскажу, как я сделал свой первый шаг к большому проекту на ИИ. Он в значительной степени изобретён с нуля, а не скопирован.
Нейросети пока не могут работать с большими проектами. Даже лучшие их образцы начинают тупить, если кода больше 40-100 кб, и галлюцинировать после 10-60 итераций одного и того же проекта (1000 циклов – это пока что грубый маркетинг). Шестьдесят итераций – это много? Если речь об автономной ИИ-разработке, то очень мало. Если о man-in-the-loop, то более-менее уже потянет.
Я выбрал такие условия:
-
реально нужную задачу (причем тема для меня в новинку)
-
незнакомый язык (т.е. чистый вайб-кодинг)
-
отсутствие RAG (забегая вперёд: чтобы никоим образом не зависеть от истории)
-
невозможность или высокую трудность сделать приложение единым блоком
Очевидно, что в таких условиях единственное решение – делить код на автономные блоки, и разрабатывать каждый блок отдельно. Это желательная практика в командах, но в реальности на нее часто забивают, поскольку команда – вот она всегда доступна на встречах, вроде все понимают друг друга, так зачем париться о грануляции?
С нейронкой это приводит к тем же проблемам, но может, получится обходной маневр?..
Первый прототип был сгенерирован как один файл (я оставил его в проекте). Он работает, он очень похож на итоговый, но в нем нет и 10% итогового функционала. Уже на второй итерации нейронка стала галлюцинировать – по мелочам (упорно путая формат лога, неспособная исправить ошибки) и по крупному (перестав понимать, что от нее требуется, как-то по-своему формулируя задачу)
Решение увиделось в упоре на гранулярность. То есть, модули должны общаться между собой максимально просто и понятно (по соглашениям, пусть даже и неявным), тогда как внутри самого модуля допускается постоянный рефакторинг – иными словами, модуль отражает не код, а возложенную на него задачу.
Этакий черный ящик, но при этом самодокументирующийся.
Первым я выделил модуль работы с паролями. Чтобы не хранить их в файле, я взял библиотеку для работы с защищенными системными хранилищами – такое сейчас есть в каждой популярной ОС. Я также вынес в модуль кусок графического интерфейса, чтобы получить самотестирование в файле. Мне очень понравилось: не нужно устанавливать среду разработки, не нужно делать конвейер сборки и отдельные тесты. На настройку всего этого обычно уходит не один день, а тут у меня ушло всего 4 дня на целое приложение. Это, считаю, большой прорыв, поскольку сюда входит проектирование, прототипирование, тестирование в несколько этапов, с несколькими версиями, с полным пониманием всего, что происходит.
Следующим был выделен модуль выполнения скриптов. Ему добавилась задача уметь выполнять как на сервере, так и локально, а это две разных библиотеки, увы. К счастью, здесь сработал автоматизированный подход: нейросеть просто повторяла одну и ту же задачу раз десять, накапливая ошибки в логе, и в конце-концов нашелся хороший рабочий подход. Проблема была то в настройке логов, то в кодировках, то в чем-то еще. И да, работу с кодировками в ps1/bat/cmd пришлось вынести в отдельный экспериментальный файл с тестами. Мы вместе проработали несколько подходов, и самый лучший (но не идеальный) отправился в новое ТЗ, а не в код – так естественным образом образовался новый паттерн работы.
Далее я выделил модуль автоопределения типа скрипта, чтобы без лишних проблем выполнялся не только sh/bash, но и powershell, ansible/yml без хоста, python и js. Это может быть удобно и в перспективе делает программирующего агента, который я надеюсь тоже здесь опубликовать.
Теперь самое главное – в чем я увидел отличия работы с нейронкой от работы с командой живых кожаных разработчиков.
Во-первых, нейросети в общем виде не заточены на память контекста. Да, можно сохранить диалог и при необходимости поднять его – “вот тут мы поменяли интерфейс, поправь тип”. Но надо понимать, что в LLM всегда загружается вся история, плюс что-то там еще. А это – избыток токенов и, следовательно, всегда приводит к потере качества. Поэтому каждый диалог лучше начинать с текущей версии модуля и инструкций к нему, как будто работаешь с новым человеком. История – длинные чаты или RAG – оказалась ненужной. Есть только самодокиментированный (иногда даже без комментариев!) код и есть текущее задание по нему. Лаконично. Встроенных тестов должно быть досатточно для понимания нейронкой вашего интерфейса, или можно их описать отдельным файлом-примером, но даже такой подход со временем теряет актуальность, поэтому эволюционно у меня остались только модули с кодом, которые вполне самодостаточны.
Во-вторых, как выше писал, внутри себя модули постепенно превращаются в черные ящики. Они рефакторятся без вашего ведома, или с небольшим комментарием на эту тему. Количество багов при этом уменьшается, так как нейронка постепенно сводит всё к тому, что лучше всего понимает (о да, модуль обязан быть семантически устойчивым островом!). Git перестает быть пошаговым логированием мыслей разработчика и превращается в логирование стабильных интерфейсных срезов, ну что-ж, чем-то всегда приходится жертвовать.
Третье – стратегия предотвращения галлюцинаций. Нужно подбирать пары нейросетей с разной базой, разные под разные языки. Для питона у меня хорошо зашла схема: 90% кода делать на qwen-3.5-max, а оставшееся, когда Цюен начинает глючить, выполнять на gemma-4-it (это очень дешевые LLM, дорогие работают лучше). Есть много агрегаторов, в т.ч. российских, которые позволяют прозрачно переключаться между моделями. При подходе с короткими диалогами цена может быть очень небольшой, порядка 20-150р/день. Использование новой нейронки с рефактором обычно прерывает галлюцинации, потому что их природа – в самоусилении ошибки, а здесь мы как бы убираем сам источник ошибки, и осцилляции начинаются заново.
И, повторюсь, абсолютно критичным становится умение выделять блоки, чтобы общение между ними было сведено до минима и чтобы они могли тестироваться по отдельности.
Демонстрационный проект все же мелковат, а как перейти к действительно большому проекту? Пока что просто использую тот же самый подход – выцепляю по 2-30 файлов из своего проекта (но в этот раз с отдельными тестами), скармливаю их нейросетям в режиме JSON и сразу запускаю тесты. Возникает интерфейсный дрейф – выходные форматы могут случайно измениться, а входные – обрасти ненужными проверками, не влияющими на приложение, но ломающими тесты. Но с этим понятно как работать – использовать все более строгие описания интерфейсов.
Что для меня не взлетело: Cursor, Qwen studio, Copilot. Да, они работают, но не так быстро и не так точно, как хотелось бы. Возможно, их дизайн ограничен обратной совместимостью и ожиданиями пользователей. Но возможно, я слишком рано забросил их, и следует посмотреть, что там в обновлениях…
Прежде чем выкладывать программу, я настроил с ее помощью то, что хотел, и убедился, что работать с ней легко и для админской песочницы – предпочтительнее других просмотренных инструментов. Это потребовало огромного количества изменений, и я даже понял, отчего заказчики бывают такими невыносимыми занудами:)
Подытожу принципы, сразу с их возможной эволюцией.
-
Максимальная гранулярность. Это то, к чему всё идет – не личное мнение, а закон сложных систем. Еще Илья свет Пригожин вывел, что сложные системы стремятся от множества маленьких элементов к небольшому числу средних, с четкими протоколами взаимодействий. Его универсальный подход полностью оправдывает себя и в программировании, и тогда грануляция получает возможность эволюционировать.
-
Самотестируемость модуля. Модуль должен содержать встроенные тесты или тестовый запуск прямо в своем файле. На период прототипа – прекрасно, но в дальнейшем, видимо, будет напрягать.
-
Короткая память, отказ от хранения диалогов. Может, в будущем поменяется, но сейчас предпосылок нет.
-
Ротация моделей. Применять при возникновении повторяющегося тупежа.
-
Модуль как серый ящик. Это необходимо для работы п.1, а без него никак. Магический артефакт? Ну так к тому всё и идёт – поэтому важно совершенствовать управление артефактами.
-
Масштабирование выделением логических фрагментов. Очень перспективное направление, позволяет не только работать с легкими моделями и улучшать качество тяжелых, но и не дает всему коду скомпрометироваться: один блок уходит только одному провайдеру и нейронке, другой – только другому, третий вообще обрабатывается локальной LLM, и служба безопасности довольна.
Всем успехов, результат выложил здесь
Всем успехов, результат выложил здесь
Автор: fathergorry


