Меня зовут Алекс Гусев и сегодня я расскажу о том, как ChatGPT убедил меня переписать библиотеку @teqfw/di, которую я бережно "выращиваю" с 2019-го года, и почему я всё-таки убедился.

Эта JS-библиотека позволяет мне использовать в своих веб-приложениях позднее связывание и даёт возможность писать изоморфный код, который без изменений работает и в браузере, и на бэке. Без транспиляции исходников, без ручной регистрации зависимостей - так, как я привык делать в Java и PHP. Я почти 7 лет вручную выверял каждую строку этой библиотеки, а на прошлой неделе я отдал её на откуп Codex-агенту и вот что он с ней сделал.

КДПВ по песне "Два корабля" группы "Агата Кристи".
КДПВ по песне "Два корабля" группы "Агата Кристи".

Что было у меня

У меня была библиотека, которая позволяла отказаться от статических импортов в прикладном коде (раннее связывание) и реализовывала принцип Inversion Of Control через внедрение зависимостей в конструктор (позднее связывание). Типовой прикладной код у меня выглядел так:

export default class Namespace_Package_Module_Component {
    constructor({
        Namespace_Package_Module_Dep1: dep1,
        Namespace_Package_Module_Dep2: dep2,
    }) { ... }
}

Мой контейнер объектов (после конфигурации, разумеется) анализировал ключи объекта, передаваемого в конструктор, и создавал по именам ключей нужные конструктору зависимости (у меня много статей на эту тему, там вся механика описана в деталях).

При необходимости в зависимости можно было добавлять библиотеки самого nodejs или пакетов из ./node_mpdules/ (через префикс node: в имени ключа). В общем, под конец у меня вообще статические импорты были только в composition root (bootstrap) и самой @teqfw/di.

Время перемен

Беда пришла, откуда не ждали - со стороны LLM-агентов. В IT-сфере сложилось публичное представление, что LLM-агенты должны помогать создавать код. Что они натасканы на тоннах примеров и затыкают за пояс не только джунов, но уже и мидлов. Всё, что нужно - нормальные понятные промпты!

Ну, я ж не мог остаться в стороне от такого движа - я попробовал (раз, два, три). Да, действительно - агенты (как минимум, Codex) могут создавать код, если им дать понятные инструкции. Они пишут ужасный (с моей точки зрения) код, но он работает. И главное - они делают это быстро. Очень быстро. Почти так же быстро, как сказать "черничный пирог".

Да, у меня несколько необычный стиль связывания JS-файлов, специфический стиль оформления es6-модулей и применения JSDoc-аннотаций. Но это всё решалось обычной документацией. Агенты - ребята дисциплинированные, кто бы что бы там ни говорил про их стохастическую природу.

Разумеется, я не мог не попробовать поручить агентам разработку самого базового своего пакета - @teqfw/di. И на прошлое неделе я это сделал.

Что стало у агента

Мы с GPT-чатиком всю неделю сидели писали корпус документации согласно моей же методологии ADSM (тоже в моих публикациях есть и про это). Ходили кругами от уровня к уровню, выравнивали описание, терминологию, функционал, архитектуру, соглашения и т.п. Ну, я-то всё и так знал, но нужно было доступно изложить это в виде контекстной документации агентам, которые будут писать код (вернее - переписывать).

С агентами было полное взаимопонимание - под моим чутким давлением до самого момента генерации кода библиотеки. Я решил слегка изменить грамматику кодирования зависимостей, чтобы можно было заставить tsserver в vscode понимать создаваемый код через JSDoc-аннотации. Было там пару неприятных моментов в моей текущей грамматике, что конфликтовали с JSDoc.

Ну я и дал в этом вопросе немного свободы агенту. Агент выдал - отныне ты в прикладном коде будешь описывать зависимости так:

export const __deps__ = {
    default: {
        dep1: 'Namespace_Package_Module_Dep1',
        dep2: 'Namespace_Package_Module_Dep2',
    },
};

export default class Namespace_Package_Module_Component {
    constructor({ dep1, dep2 }) { }
}

"Ух! - говорю я. - Здорово! А зачем так? Это ж удвоение мест описания зависимостей!! А если ещё и @typedef аннотацию JSDoc добавить - то и утроение!!!"

А он мне и отвечает - "А мне так удобнее! Мне всё равно, одно-два-три места, если по шаблону. Мне ваш DRY до лампочки, особенно если всё в одном файле и ходить никуда не надо."

Ну и начал давить аргументами, типа твой способ сборки зависимостей работает только рекурсивно (а то я не в курсе!), при создании объекта контейнером, а мой (т.е. - его) способ позволяет проанализировать всё дерево зависимостей без создания объектов (ну, аргумент, не спорю).

Резюме

Знаете, что стало железобетонным аргументом со стороны агента? Практика! Мало того, что он за пару подходов соорудил в библиотеке код (ужасный код, повторяю, но - рабочий!), который привносит (уже по его схеме) в JS позднее связывание без транспилции и ручной регистрации зависимостей в контейнере (его контейнере!). Так он ещё за одну итерацию (2-3 минуты) перевёл мой последний проект со старой схемы декларации зависимостей (моей) на новую (свою).

Мы создавали правила разработки ПО для людей, а теперь нужно переписывать эти правила под агентов. А агентам DRY до лампочки, так-то. Им нужна ясность:

export const __deps__ = {
  configManager: 'Ttp_Back_Configuration_Manager$',
  storage: 'Ttp_Back_Storage_Repository$',
  aggregateFactory: 'Ttp_Back_Aggregate_Factory$',
  telegramReader: 'Ttp_Back_External_TelegramReader$',
  telegramPublisher: 'Ttp_Back_External_TelegramPublisher$',
  llmTranslator: 'Ttp_Back_External_LlmTranslator$',
  promptProvider: 'Ttp_Back_Prompt_Provider$',
  logger: 'Ttp_Back_Logger$',
};

Поэтому и вот. Моралей не будет.

Комментарии (11)


  1. Deeens
    25.02.2026 20:16

    Понял, скачиваю и сохраняю софт ДО появления ИИ и записываю на DVD и MO дисках, что бы нельзя было поменять код. Черное настоящие уже наступило


  1. nin-jin
    25.02.2026 20:16

    Я, конечно, не Codex, но вы как страдали ерундой 7 лет, так продолжаете. Всё делается куда проще:

    export class My_foo extends My_object {
        get bar() { return this.$.My_bar } // late singleton binding
    }
    
    export let My_bar = 'bar'
    
    export class My_component extends My_object {
        @mem get foo() { return new this.$.My_foo } // lazy unique instance
        @mem get bar() { return this.$.foo.bar } // memoized deep deps
    }


    1. flancer Автор
      25.02.2026 20:16

      Коллега, вы как жили в мире розовых пони (@mem), так там и живёте. Не выходите оттуда. Я с вами уже говорил на эту тему.


      1. nin-jin
        25.02.2026 20:16

        Реализацию сего тривиального декоратора можете спросить у своей любимой нейронки.


        1. flancer Автор
          25.02.2026 20:16

          А вы, коллега, правда считаете, что придумать декоратор - это круто?

          Я, вот, например, считаю, что круто - это обойтись без декоратора. Так, чтобы работало на уровне базы ;)


          1. nin-jin
            25.02.2026 20:16

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


            1. flancer Автор
              25.02.2026 20:16

              У нас с вами, коллега, очень сильно разное чувство прекрасного. Моё, например, страдает от такой картины:

              И это только с $hyoo_, а ещё с $mol_ не  меньше
              И это только с $hyoo_, а ещё с $mol_ не меньше

              Разработчики языка специально в ES6 сделали возможность ограничивать область видимости объектов (let vs var). Понимали, что чем менее доступен объект, тем меньше поверхность атаки на него.

              А разработчик $mol всё выложил в globals - скучное техническое решение с весёлыми последствиями. Каждый-всякий-любой может на странице https://mol.hyoo.ru/ сделать вот такое (в консоли или скриптом):

              window.$mol_view=null

              и получить такое после клика на любой ссылке:

              Любой скрипт на странице может "убить" $mol
              Любой скрипт на странице может "убить" $mol

              А ведь, при желании, можно не просто тупо грохать приложение, а обернуть любой $mol-объект собственным кодом прямо с консоли браузера. Можно это представить как непревзойдённую расширяемость, а можно - как дыру для утечки конфиденциальных данных.

              А попробуйте-ка с консоли получить доступ к моему контейнеру и его объектам хотя бы вот в этой демке (к этой статье). Ну как? О!

              ES6-модули рулят с 2015-го года:

              <script type="module">

              но ваша архитектура до сих пор тупо лезет в глобалс и подставляется через него :(

              А что касается бойлерплейта, то в наш век автоматизации разработки ПО этот аргумент звучит жалко.


              1. nin-jin
                25.02.2026 20:16

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


                1. flancer Автор
                  25.02.2026 20:16

                  Да, я действительно по-другому, нежели вы, понимаю, как работает JS runtime. Но вы лично, и любой другой, можете повторить то, что я написал выше. Это скриншоты, а не нейрогенерёнка. Это повторяемо.

                  Размещать что-либо прикладное в globals после 2015-го года - это не олдскульно, а архаично.

                  console.log=null

                  Вы, так-то изменили не объект, который мой контейнер генерирует, а окружение, в котором мой контейнер работает. Надеюсь, вы понимаете разницу :) К моим прикладным объектам (и самому контейнеру) из консоли у вас доступа нет.


                  1. nin-jin
                    25.02.2026 20:16

                    Никакой разницы
                    Никакой разницы


                    1. flancer Автор
                      25.02.2026 20:16

                      Неплохо! Это всё ещё окружение, но тем не менее - это реальная возможность переопределить поведение порождаемых объектов.

                      В любом случае, спасибо за аргумент, коллега :) Меня тут на Хабре как-то уже ногами месили за то, что я использую замыкания в конструкторе вместо того, чтобы как "все нормальные люди" использовать классы и прототипы. Теперь, благодаря вам, я могу наглядно показать, почему я не делаю, как "все нормальные люди".

                      Переписал имплементацию логгера на замыкание:

                      export default class Client1_Di_Logger {
                      
                          constructor() {
                              /** @type {Console|undefined} */
                              let consoleRef;
                      
                              this.setConsoleLogger = (console) => {
                                  consoleRef = console;
                              };
                      
                              this.print = (msg) => {
                                  consoleRef.log('Client1 logger: ' + msg);
                              };
                          }
                      
                      }

                      Теперь ваш трюк с Client1_Di_Logger не работает. Остальное в примере тоже можно через замыкания переписать (я пример специально делал в виде, наименее шокирующем нормальных людей). Принцип тот же. Но я думаю, вы уже и сами это поняли.