Информация
- В рейтинге
- 3 435-й
- Откуда
- Рига, Латвия, Латвия
- Дата рождения
- Зарегистрирован
- Активность
Специализация
Фулстек разработчик, Архитектор программного обеспечения
Ведущий
JavaScript
HTML
CSS
Node.js
Vue.js
Веб-разработка
Progressive Web Apps
PostgreSQL
MySQL
GitHub
Сделал при помощи codex-агента скилл на основе этой публикации - architecture-foundations. Можно попробовать применить с ИИ-агентом (claude, codex, opencode,...). Установка через
npx:или вручную, через копирование каталога architecture-foundations в
~/.agents/skills/.Интересно попробовать описанные в публикации оси для анализа своих проектов.
Спасибо, посмеялся. Я уже на одной четверти текста перестал связывать одно с другим, а для тиктокера это вообще непролазный лес! Но в целом - я в восторге!! Никогда не смотрел на разработку программ в таком ключе. Получилось очень интересно и познавательно (ну, там, где я понял о чём речь, разумеется :))) ).
Попросил ИИшечку привести пост к моему когнитивному уровню - вроде стало чуть полегче. Выкладываю выжимку, может кому сгодится. Там, правда, под меня "заточено", но каждый может перегнать страницу в PDF, скормить "своей" ИИшке и получить трансляцию идей ТС'а "под себя".
Вот "под меня"
Интересная статья, но, на мой взгляд, её ценность не в том, что программирование буквально «доказывает» Платона, Аристотеля или Лейбница. Философские параллели здесь скорее язык, позволяющий разложить архитектурные решения по независимым вопросам.
В упрощённом виде автор предлагает спрашивать о системе следующее:
Где находится общее: в явно объявленном типе, в фактической структуре объекта или только в именах и соглашениях?
Что первично: устойчивый объект с изменяемым состоянием или поток событий, из которого состояние каждый раз выводится?
Кто может менять состояние: любой обладатель ссылки или только сама сущность через обработку сообщений?
Сохраняется ли форма в runtime: может ли система во время исполнения распознать тип, схему и смысл полученного сообщения?
Кто владеет исполнением: внешний поток, единый event loop, coroutine, actor или отдельный процесс?
Где находится цель: только у пользователя либо представлена внутри системы и самостоятельно ею преследуется?
Из этого получается несколько вполне практических выводов.
Первый: главная проблема конкурентности — сочетание нескольких исполнителей с общим изменяемым состоянием. Лечить её можно в двух независимых направлениях: убрать изменяемость или убрать совместное владение. Отсюда два устойчивых подхода: свободно разделяемые immutable-значения и изолированное mutable-состояние с единственным владельцем.
Второй: у каждого изменяемого состояния должен быть понятный хозяин. Остальные компоненты не должны менять его напрямую — они предъявляют команду или сообщение, а владелец сам решает, как и когда его обработать. Это снижает число возможных комбинаций взаимодействий, хотя оплачивается очередями, сериализацией, задержками и необходимостью согласования.
Третий: интерфейс полезен именно как узкий контракт, а не как способ передавать потомкам кусок готового «бытия». Чем больше состояния и поведения спускается через наследование, тем сильнее связанность и тем сложнее независимо изменять части системы. Поэтому композиция и чистые интерфейсы обычно устойчивее глубоких иерархий классов.
Четвёртый: event sourcing не отменяет объекты, а переносит источник истины. В обычном ООП истина находится в текущем состоянии объекта. В event-sourced-системе — в журнале, а объект становится проекцией истории. Это полезно там, где важны аудит, восстановление, повторная обработка и объяснимость, но не является универсально лучшей моделью.
Пятый: runtime-системе необязательно иметь рефицированные типы самого языка, но ей необходимо какое-то доступное представление протокола — schema, discriminator, валидатор, таблица диспетчеризации или сгенерированный декодер. Замкнутый компонент может самостоятельно интерпретировать сообщение только тогда, когда формат сообщения представлен во время исполнения.
Шестой: автономность исполнения и наличие цели — разные свойства. Actor может иметь собственный цикл обработки и ни к чему не стремиться. Оптимизатор может преследовать цель, оставаясь полностью управляемым внешней петлёй. Для агентных систем это различие принципиально: отдельно должны проектироваться движитель, цель, полномочия и возможность её коррекции.
Наиболее сильной мне кажется последняя часть статьи про LLM-агентов. В обычной системе сообщение и код разделены: данные не должны переписывать поведение обработчика. У языкового агента и инструкции, и данные, и описание цели представлены одним субстратом — текстом. Поэтому message passing сам по себе не обеспечивает изоляции: сообщение может изменить не состояние, а интерпретацию правил и цели.
Практическое следствие — на внутренних границах агентных систем свободный естественный язык, вероятно, должен уступать место более жёстким протоколам:
явно заданному типу сообщения;
разделению данных, инструкций и системных правил;
валидируемому payload;
ограниченным capabilities;
аудируемому журналу действий;
отдельной процедуре изменения цели и полномочий.
Для мультиагентных систем из этого следуют два относительно устойчивых варианта. Либо один большой агент с внутренним разделением ролей, единым контекстом и общей целью. Либо несколько изолированных агентов с явными ролями, полномочиями, аудитом, арбитражем и процедурами согласования. Толпа агентов, свободно общающихся на естественном языке при размытых целях и широких правах, выглядит действительно неустойчивой.
При этом я бы осторожнее относился к сильным тезисам статьи. Аналогия между actor и монадой или между Paxos и предустановленной гармонией красива, но сама по себе ничего не доказывает. Заявленная ортогональность осей тоже не абсолютна: в реальных языках и runtime эти свойства часто сцеплены. А «закон сложности» работает только тогда, когда систему действительно можно разрезать по узким границам. Плохо декомпозированная распределённая система легко оказывается сложнее хорошего модульного монолита.
Поэтому я бы сформулировал практическое ядро статьи так:
Большие системы становятся понятнее, когда состояние имеет явного владельца, взаимодействия проходят через узкие проверяемые протоколы, а форма, исполнение, полномочия и цели не смешиваются в одной конструкции.
Для агентных систем к этому добавляется ещё одно требование: изолировать нужно не только память, но также интерпретацию, инструкции, полномочия и цель. Здесь обычной инкапсуляции уже недостаточно — появляются роли, политики, аудит, арбитраж и ответственность.
В любом случае, есть над чем подумать. Спасибо за статью! (y)
Лично я решаю с помощью DI проблему "распределённой разработки кода слабосвязанными командами разработчиков". Я очень быстро забываю контекст проекта. Через полгода уже не помню ничего, если в течение этого времени не заглядывал код. Так что "я сейчас" и "я через год" - это и есть слабосвязанные команды разработчиков :)
А в принципе, весь IoC для этого был придуман. DI - лишь один из вариантов.
Смотрите, я привёл код, который "на пальцах" демонстрирует идею позднего связывания и создания JS-кода без статических импортов. А вы говорите, дайте "нормальный пример". Я уже дал. Просто вы не видите, где его можно применить. Я дам вам другой пример (вот интерфейс, вот место применения, а вот - имплементация в другом пакете), и вы скажете, что это же можно сделать и со статическими импортами. И будете совершенно правы. А кто-то скажет, что это же можно сделать и на любом другом ЯП или даже сразу в машкодах. И тоже будет совершенно прав.
Дело не в правоте, а в возможности решать задачи наиболее удобным для вас путём. Если вы не видите, где применять JS-код без статических импортов или использовать позднее связывание - вам просто это не нужно. Было бы нужно, вы бы зацепились глазом за пример выше и вас бы сразу триггернуло. Я вас уверяю, это и есть нормальный пример. Там всё видно. По крайней мере тем, кому это надо или может быть надо.
DI - это про внедрение зависимостей. Зависимости есть в любом коде, состоящем из более, чем одного файла. Статические импорты в JS - это тоже зависимости.
DI в JS можно вообще без классов использовать:
Вот архив с кодом - достаточно распаковать, затем
npm install&npm start.Если кодовая база большая, то нужен какой-то механизм для связывания отдельных файлов. В JS для этого используют импорты - статические и динамические. DI - это как раз и есть связывание отдельных исходников в единую кодовую базу через динамический импорт. ООП тут вообще перпендикулярно стоит ко всему этому.
DI-контейнер - это никакая не революция. Им сто лет в обед. Я со Spring'ом работал в 2010-х. Это уже была 3-я версия платформы. Затем я использовал DI в PHP - в Magento. Очень своеобразный DI, особенно в первой версии (2008 - 2015 год). Использовал DI из Symfony 2. В PHP вообще с этим делом всё хорошо, даже стандарт есть - PSR-11.
Когда окончательно перешёл в JS даже "запилил" свой DI - чтобы писать в привычной для себя манере. Правда я позволил ИИ его "под себя" переписать (под "силиконовых", а не под "кожаных").
Что касается примеров кода, то могу предложить посмотреть вот этот код - @flancer32/teq-cms. Я писал его "ручками", ещё на "рукописном" DI. А код в @flancer32/teq-web уже написан ИИ (Codex) с "его" DI. Я в код заглядывал, но руками старался не править ничего. Самый, что ни на есть "машинный код".
Рабочий код на фронте - вот. Зайдите в DevTools и посмотрите, что видит браузер:
Это старый код, но сделан тоже в той же IoC-парадигме с моим рукописным DI в основе. Для построения UI используется vue и Quasar.
Если вы посмотрите все es6-модули, то там не будет ни одного (!) статического импорта. Статические импорты нужны в головном скрипте для загрузки DI-библиотеки, а дальше контейнер сам динамически (через
import()) загружает нужные зависимости по мере необходимости. Как следствие, код становится изоморфным (без изменений может использоваться как в браузере, так и в nodejs). Вот вообще без изменений! Поначалу я на бэке импортировал node'овские библиотеки, но сейчас и они инжектятся. То есть, можно и бэкендовый код в браузере запускать, только node-библиотеки нужно замокать (делается через препроцессор контейнера). А сам код приложения остаётся без изменений.И в отличие от многих других DI-контейнеров (включая контейнер автора статьи), у моего контейнера ограничена возможность что-то зарегистрировать специально:
У меня регистрировать можно только если перевести контейнер в тестовый режим:
То есть, в prod-режиме все es6-модули резолвятся и подгружаются автоматически, сколько бы их там ни было. Нужно только "потянуть" головной модуль, а дальше строится дерево зависимостей и подтягиваются исходники (можно прямо с unpkg.com тянуть). Более того, с учётом наличия в контейнере препроцессора, можно менять маппинг идентификаторов зависимостей на исходный код и реализовывать такие посторонние для "ванильного" JS вещи, как интерфейсы. А с учётом наличия там же постпроцессора, можно использовать в JS и AOP.
Все эти приёмы и технологии давным давно используются в программировании и никакая не революция. Просто они используются там, где кодовая база проекта начинает заваливать за миллионы строк кода (в Magento 2, например, больше 2 млн. и для работы с JS используется requirejs, тоже своего рода DI).
Много у вас примеров таких объёмных кодовых баз именно на JavaScript? А для кодирования на одной веб-страничке чего угодно такие навороты действительно не нужны.
IMHO, всё сильно поменялось, а чувак сделал глобальный вывод на основе своих локальных про... махов - "миддлы не вытаскивают!!".
А может пора уже в новых условиях всю эту "табель о рангах" вместе со сторипоинтами заменить на что-то другое? На те же токены, например, тип модели и уровень reasoning'а? На одной и той же задаче 5.6 Sol на light reasoning даст результат, очень отличающийся от результата 5.6 Luna на high reasoning. И не всегда в лучшую сторону.
Про "сделать видимым контур проверки результата" - мысль дельная. Но об этом писали буквально в первых публичных версиях GPT: "ChatGPT может допускать ошибки. Рекомендуем проверять важную информацию."
В целом - статья годная и полезная. Вопрос - уже половина ответа.
А почему только для Codex? Разве другие агенты не работают с "распределённым AGENTS.md" таким же образом? Лично я в основном работаю именно с Codex'ом. Немного работал с opencode, но там я визуально не контролировал, что агент считывал всю иерархию AGENT.md до целевого каталога (с Codex'ом я это проверял). Думал, что все агенты действуют по такому же принципу - считывают все AGENTS.md от корня проекта и до рабочего каталога. Это не так?
UPDATE:
Проверил opencode. На промпт "Сколько файлов AGENTS.md участвует в формировании инструкций для этого каталога. Перечисли все." Получил такой ответ:
"Этот каталог" бы указан ранее.
Похоже, что opencode тоже видит файлы AGENTS.md в иерархии. Что логично, по такому же принципу работают конфигурационные файлы
.htaccessдля apache.UPDATE2:
Это для Copilot'а:
Copilot также перечислил все AGENTS.md, лежащие в иерархии на пути к целевому каталогу. Каждый AGENTS.md содержит инструкции для своего уровня и в совокупности собираются в единый пул инструкций для рабочего каталога.
Вот у меня отдельный AGENTS.md на каждый важный для проекта каталог (112 строк для корневого). Я уже про это писал. Агенты (codex как минимум) читают все AGENTS.md от корня проекта к рабочему (для агента) каталогу. Правила для агентов можно (и нужно!) "размазывать" по всему проекту.
Я откушал полной ложкой то, что вы называете "their own medicine". А вот вы в это время "ели" что-то другое.
"Мы есть то, что мы едим." (с)
А так всё верно. Самый надёжный способ проверить, что человек умеет плавать - бросить его в воду. Все, кто выплыл - 100% умеют.
Какая "сотня активных пользователей"?!
Там один клиент, которому надо открыть в 4 раза больше магазинов за год, чем он это делал вручную. Если клиент способен оплатить 6 месяцев работы команды из 3 человек и кучи агентов, а в ответ получает выполнение своих планов ("закрытие боли"), то... какая там разница что за код нагенерировал
компиляторИИ, если код выполняет свою работу?Кстати, ИИ вполне себе нормально код генерирует, он архитектуру не держит. А код сгенерирует в любом стиле, особенно если есть скилл под этот стиль.
Это не стартап, а одноразовое приложение, для одного клиента: "Продукт ответил на вопрос бизнеса: сказал, где строить, — и клиент пошёл строить".
Для следующего клиента нужно уже на этих же инструментах и методологии писать другое приложение.
Из очевидного: доработка таких "одноразовых приложений" до новых нужд клиента - довольно дешёвый процесс:
Отсюда вывод - ценность разработки смещается не в код, а в документацию к коду.
А знаете где эта проблема уже решена на уровне архитектуры? В языке, который не рекомендуют использовать для создания браузерных приложений - в JavaScript.
Чистейший late binding! Приятно видеть, что технологии "кровавого энтерпрайза" прорастают в браузерных приложениях.
Да, несколько непонятно, что именно нужно делать в игре. Нужно разбираться методом проб и ошибок. Но в целом - впечатляет (y) Интересно, а насколько долго в неё можно играть? Насколько большая "игровая Вселенная"? Если это "браузерка", то все возможные повороты сценария должны быть предопределены и загружены. Если, в теории, я выполню все квесты, то - всё?
Я называл такой подход "иерархией AGENTS.md", "слоистый" - интересный подбор терминов. Необычный, как по мне. В любом случае, ваш опыт также подтверждает, что множественное использование AGENTS.md в одном проекте - вполне себе практическое решение. А то мне за ту статью минусов в панамку напихали, типа нейрослоп. Ну, да - нейрослоп. Специально сжатый. Не умеем мы ещё читать нейрослоп. Даже специально сжатый.
Это так, мысли вслух. Удачи в эволюции приложения!
Неудачный пример. Я, например, могу выключить газ с закрытыми глазами. Да и вы тоже. Попробуйте просто. С электроподжигом я могу и включить газ с закрытыми глазами - горящий газ шумит по-другому. Я, когда дома чайник ставлю, никогда специально под него не заглядываю - ни когда включаю, ни когда выключаю. У меня плита с электроподжигом.
Или у вас действительно были случаи, когда вы газ выключили, а он продолжил гореть? Я лично с таким не сталкивался ни разу.
А вы правда верите, что качество кода зависит от того, что на него кто-то посмотрел?
Интересный взгляд. Плюсанул. Мне кажется, что заполнять своё контекстное окно нужно "маловероятным". LLM тяготеют к высоким вероятностям, а низкие вероятности остаются не при делах.
Свершилось то, о чём предупреждал Иоанн Богослов в своих "откровениях":
Это как раз про вероятности. "Кожаным" придётся уходить в сторону отклонений (холоден или горяч), т.к. срединная ниша (тёплый) теперь занята "силиконовыми".
Это, разумеется, шутка, но в каждой шутке есть только доля шутки. IMHO, творческая составляющая развития (эволюция) - за человеком, стабилизирующая (удерживающая) - за ИИ.
Ключевая проблема в том, что при разработке приложений вы "думаете" не на том языке, на котором "думает" компьютер при их выполнении.
В природе существуют транспиляторы в JavaScript, как минимум, для следующих языков: CoffeeScript, Dart, Kotlin, Scala, F#, Clojure, Elm, PureScript, Haxe, Go, Java, Python, Ruby, ...
Простую "программу для браузера" можно написать на любом из этих языков, но её придётся транспилировать в JS (или WASM) - браузеры просто не понимают ничего, кроме JavaScript и WASM (но WASM уже плохо понимает человек).
Каждый из перечисленных выше языков обладает какими-то преимуществами и какими-то недостатками, вытекающими из преимуществ. При транспиляции из одного языка в другой недостатки каждого языка суммируются, а преимущества исходного языка просто теряются.
Так вот, чем TypeScript кардинально лучше любого из этих языков? Тем, что похож на JavaScript больше любого другого? Больше любого другого на JS похож сам JS - в нём недостатки только его самого, которые хоть как-то компенсируются его достоинствами.
TS можно и нужно было использовать вместо JS до появления ES6 - c 2012 по 2015 год. А потом качественная разница между ними лишь уменьшалась.
Если вам удобно кодировать "программы для браузера" на TypeScript - пожалуйста, кодируйте на TypeScript. Другим удобно это делать на других языках (я перечислил только часть). Браузер всё равно понимает лишь JavaScript и WASM.
Когда я пишу "программы для браузера", я предпочитаю делать это на максимально близком ему языке, чтобы не привносить в runtime недостатки других ЯП. Только и всего.