All streams
Search
Write a publication
Pull to refresh
50
-0.1
Alex Gusev @flancer

Я кодирую, потому что я кодирую…

Send message

В исследовании приняли участие 500 взрослых людей, имеющих опыт использования языковых моделей

В этом документе я попытался сформулировать базовые принципы, на которых я строю свою разработку. ESM и без транспиляции - вот это база. Детализация этих принципов "ещё в пути".

Если коротко, то чтобы можно было строить достаточно большие JS-приложения (для меня это 100-200 таблиц в БД) нужна декомпозиция всей кодовой базы на достаточно малые фрагменты (желательно два-три экрана кода без документации), описание интерфейсов этих фрагментов и инструмент по склейке всех фрагментов обратно в единую кодовую базу с учётом описанных интерфейсов (у меня это - Контейнер Объектов). Не надо держать в голове сразу всю кодовую базу, достаточно отдельных рабочих фрагментов и их зависимостей. Контейнер ориентируется по их идентификаторам и сообщит при старте, если чего-то там ой.

Если есть какой-то небольшой проект на JS, могу помочь настроить Контейнер Объектов, чтобы он автоматически находил нужные исходники, и объяснить, какие идентификаторы зависимостей указывать, чтобы Контейнер возвращал нужный объект в нужном lifestyle. Для начала желательно чистый nodejs - будет сильно проще. Также опыт работы с DI в других ЯП сильно приветствуется. Но нужно будет "сменить веру" - вообще отказаться от статических импортов в своём коде ;) Даже для node'вских модулей и пакетов. Статикой подгружается только сам Контейнер. Дальше всё через динамический импорт. Все привычные инструменты, завязанные на статических импортах, придётся выкинуть.

А Вы можете привести пример ЯП, который даёт такую гарантию на 10К контрактов? Причём не для теоретического "коня в вакууме", а для реального приложения со вводом-выводом, сетевым взаимодействием и пользовательскими данными? С учётом, что для веб-приложений это гарантия консистентности всего стека - от поля в форме на странице и до колонки в таблице в БД?

Нет, предлагаемый мной подход не даёт никому никаких гарантий. Пробуйте, если хотите, на свой страх и риск. Я долгое время имел дело с платформой Magento (порядка 4М SLOC во второй версии, и это без огромного количества плагинов к ней). Она никому ничего не гарантировала, но при этом как-то работала. На популярных use-case'ах - очень хорошо, на экзотических - очень плохо. Зачастую, после обновлений, приходилось брать в руки "напильник" (отладчик) и находить и править баги (не все, а только те, которые мешали жить пользователям). Я это называю "Magento way". В том числе и поэтому у меня любовь к "неизменённому коду" - приходилось отказывать от хороших, но обфусцированных плагинов, из-за невозможности оперативно интегрировать их в приложение в случае изменений (а они есть всегда) платформы или других плагинов.

Более того, я гарантирую, что когнитивный контекст каждого участника разработки уникален и отличается от контекста другого участника. Поэтому и нужны все эти митинги и документация, чтобы хоть как-то приводить все эти контексты в хоть какое-то соответствие. Лично для меня "источником правды" является код - что он делает, то и есть (отладчик в помощь, если непонятно). Да и то - нужно ещё посмотреть на окружение, в котором запускается этот код. А в документации может быть написано что угодно. Хорошая документация лишь помогает понять код "от общего к частному" (иерархически выстраивает когнитивный контекст, который адекватен текущей ситуации и помогает понять существующий код).

Да и вообще, я не считаю добром "тотальное описание типов". Я считаю добром "эволюционную устойчивость кода". Думаю, что это в какой-то мере антонимы, особенно на больших системах с кол-ом контрактов 10K+.

Код остаётся в исходном виде. И это делает его "прозрачным, доступным и удобным в сопровождении". Вы просто начинаете мыслить в тех же категориях, что и среда исполнения, а не среда написания.

Да, возможность написания плагинов "в одно лицо" (в полтора - с применением LLM), а потом объединение различных плагинов от различных лиц в одно приложение - один из фокусов развития платформы. Архитектурно - чистая калька с Magento, на которой я довольно долго сидел. А уж сколько я тамошних плагинов заинтегрировал друг с другом...

Так уж получилось, что я - веб-программист и именно fullstack. Я писал статью для людей со схожими интересами :) Про недостатки JS (и про многое другое) я не написал, потому что это не является моей целью. Мне важно было сформулировать наиболее значимые принципы, которые я использую в веб-программировании, а не достоинства-недостатки языка.

Интерфейс, по факту, это документирование контракта: имена функций и описание входных-выходных аргументов. Вот пример описания интерфейса в ES6:

/**
 * Interface for user management in the application.
 *
 * This is a documentation-only interface (not executable).
 *
 * @interface
 */
export default class Fl64_OAuth2_Social_Back_Api_App_UserManager {
    /**
     * Creates a new user in the application's database.
     * @param {Object} params
     * @param {TeqFw_Db_Back_RDb_ITrans} [params.trx] - The transaction context.
     * @param {string} params.identity - Unique identifier assigned to the user by the provider.
     * @param {Object} [params.extras] - Additional user attributes (e.g., name, avatar).
     * @returns {Promise<{id: number}>} - The unique identifier of the created user.
     */
    async createUser({trx, identity, extras}) {}
}

И его имплементации:

/**
 * Implementation of the user management interface for the application.
 *
 * @implements Fl64_OAuth2_Social_Back_Api_App_UserManager
 */
export default class Svelters_Back_Di_Replace_Social_UserManager {
    /**
     * @param {Svelters_Back_Act_User_Create} actCreate
     */
    constructor(
        {
            Svelters_Back_Act_User_Create$: actCreate,
        }
    ) {

        /**
         * Creates a new user in the application's database.
         * @param {Object} params
         * @param {TeqFw_Db_Back_RDb_ITrans} [params.trx] - The transaction context.
         * @returns {Promise<{id: number|null}>} - The unique identifier of the created user.
         */
        this.createUser = async function ({trx: trxOuter}) {
            const {user} = await actCreate.run({trx: trxOuter});
            const id = user?.id;
            return {id};
        };

    }
}

Этот код работает прямо сейчас в одном из приложений.

Насчёт контроля типов со стороны языка вы абсолютно правы. Прежде чем писать без типов на JS нужно научится писать с типами, на той же Java. Я кодил с типами больше 10 лет. Мне уже можно без типов.

"Сказка в статье" - это построение когнитивного контекста для людей и LLM. Люди могут выбирать принимать этот контекст или нет. У LLM такого выбора нет :)

Вы, похоже, не совсем понимаете семантику слова "несколько". Очевидностей может быть более одной и я вам на это указал явным образом. В одной из них 99% пользователей чего-то-там не занимаются сборкой проектов. Вы же продолжаете упорствовать в том, что вы правы. Я уже с вами согласился - в своей системе ценностей вы правы. Если вы продолжите упорствовать дальше, то я с вами соглашусь во всём.

Быстрее станет сборка проекта. До которой 99% пользователям абсолютно все равно. 

Ну а мне очевидно, что "пользователи" звучали в контексте "проект". Особенно после:

Сам TypeScript не станет ни быстрее ни лучше. Он так и останется сахаром над JavaScript.

Мы с вами, очевидно, просто находимся в разных очевидностях. И - да, из вашей очевидности совершенно неочевидно следствие коллеги @SserjIrk про 99%, что вы и подтвердили. Полностью с вами согласен, что для ts-разрабов "до времени сборки проекта абсолютно не всё равно". Полностью согласен с коллегой @SserjIrk, что для 99% пользователей проектов до времени сборки нет никакого дела. Лично для меня не проблема существовать одновременно в нескольких очевидностях.

Ну так-то, по большому счёту, можно любой язык в любой другой транспилировать. Не всё, с какими-то ограничениями и потерями, но можно. Я с GWT (Java-to-JS) больше 15 лет назад плотно общался. За это время, уверен, искусство транспиляции одного исходного кода в другой выросло значительно.

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

С того, что 99% пользователей не занимаются сборкой проектов - они потребляют уже результат сборки (например, JS-код в браузере). Или вы только разработчиков за пользователей считаете? Для них - да, для них время сборки - проблема.

Поглядел на фото Альтмана в посте и почему-то вспомнил эту женщину в контексте вашего комментария.

Ну ладно, хоть про норвежцев с индусами не оспариваете :)

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

Механизм, работавший веками, сейчас движется по инерции? Вы таки зачем думаете сильные мира сего наперегонки строят себе дата-центры? Вот не дадут лично вам доступ к ИИ - и? Будете на смартфоне персональную модель запускать?

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

Если не может торговать, оно становится суверенным и тоже нищает.

Общество планеты Земля не может торговать с другими планетами. Оно суверенно, но не нищает.

Если общество не может накапливать богатства, оно не может богатеть.

С этим согласен полностью. Но на интеллектуальный прорыв эта мысль не тянет.

главной движущей силой прогресса является накопление и торговля

А я бы назвал "созидание и обмен". Но я не Дарвин и даже не Ливингстон :)

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

Так что - да, локальный, потоковый Composition Root может иметь место в IoC.

Переспал я с этой мыслью и пришёл к следующему. Я разделяю у себя код на две большие группы: DTO и обработчики. DTO порождаются через фабрики (обработчики). Обработчики зависят от других обработчиков. Некоторые обработчики не имеют своего собственного внутреннего состояния для сохранения результатов работы (обработчики-функции), некоторые имеют (обработчики-объекты). Первые принимают на вход набор параметров, обрабатывают их (в том числе и с использованием обработчиков-зависимостей) и выдают результат. Вторые делают то же самое, но при этом изменяют своё внутреннее состояние (типичный пример - кэширование результатов обработки).

Так вот, когда у нас единый Composition Root у нас все обработчики-функции, зависящие от других обработчиков-функций, будут в приложении иметь жизненный цикл синглтон. Т.е., нам достаточно иметь по одному экземпляру таких обработчиков на всё приложение. Данные (в виде DTO) независимо двигаются по иерархии обработчиков и возвращаются в виде результата, нигде не оставляя следов.

В случае с обработчиками-объектами всё сложнее. Если это объект уровня приложения (например, кэширует в памяти обращения к файловой системе), то он тоже синглтон, а если уровня, например, обработки отдельного HTTP-запроса (потока вычислений), то это уже инстанс (порождается на поток).

Проблема в том, что набор зависимостей в конкретном обработчике (функции или объекте) - это по сути тоже состояние. Если все зависимости - функции, то все могут быть синглтонами. Но как только какая-то из зависимостей является объектом потокового уровня, то вся иерархия обработчиков, строящихся на этой зависимости должна пересоздаваться для каждого потока обработки (HttpContext <= Model <= Operation <= Service <= ...).

JavaScript асинхронный и однопоточный (если не считать worker'ов) и в рамках этого одного потока выполнения программы я не вижу особого смысла пересоздавать иерархию обработчиков на каждый поток обработки (HTTP-)запроса. Разумнее передавать обработчикам (функциям и объектам уровня приложения) контекст в качестве параметра (пусть это и не DTO, а FTO).

В worker'ах же Composition Root отдельный и независимый от Composition Root основного приложения. Таким образом, Composition Root всё равно должен быть один на поток выполнения (thread, worker) и никакой иерархии Composition Root'ов я там не вижу.

Т.е., вот здесь:

> (не)связанный с родительским Composition Root

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

Лично я использую аналог HttpContext"а для передачи объектов между обработчиками (контроллерами) - положил, взял. Но можно для каждого потока обработки инициировать пустой Composition Root (контейнер объектов или что там аналогичное для AC?), (не)связанный с родительским Composition Root и это уже будет IoC с возможностью создания новых объектов при необходимости. Тем не менее, возможность порождения объектов в таком контексте всё-таки должна быть, чтобы считаться IoC. Без такой возможности HttpContext слабо отличается по своим свойствам от какого-нибудь in-memory storage.

Так что - да, локальный, потоковый Composition Root может иметь место в IoC.

Насчёт того, что это контекст - согласен. Насчёт того, что это IoC - нет. Суть IoC - в передаче управления созданием объектов в программе в отдельное место (Composition Root). Различные части программы не создают сами нужные им зависимости, а получают их тем (инъекция) или иным способом (сервис-локатор) из этого места.

В случае с HttpContext и Thread.CurrentPrincipal я полагаю, что подобные объекты создаются не централизовано, а в местах обработки соответствующих запросов, после чего передаются в качестве параметра или свойства объекта.

Да, это похоже на Ambient Context, но не хватает глобальности, чтобы им быть. Два разных объекта HttpContext не пересекаются по своим свойствам и могут существовать в приложении одновременно (в nodejs могли бы).

По сути, коллега @Deosis прав: Ambietn Context - это такой Сервис Локатор, через который части программы имеют доступ к Composition Root (или к хранилищу результатов его работы). А если предположить, что в этот контекст может писать не только Composition Root, а вообще любая часть программы, то он прав и во второй части - "возвращает значение в зависимости от фазы луны".

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

Поэтому вопрос "зачем?" чертовски занимателен.

Information

Rating
Does not participate
Location
Рига, Латвия, Латвия
Date of birth
Registered
Activity

Specialization

Fullstack Developer
Lead
From 3,000 €
JavaScript
HTML
CSS
Node.js
Vue.js
Web development
Progressive Web Apps
PostgreSQL
MySQL
GitHub