Information
- Rating
- 3,255-th
- Location
- Рига, Латвия, Латвия
- Date of birth
- Registered
- Activity
Specialization
Фулстек разработчик, Архитектор программного обеспечения
Ведущий
JavaScript
HTML
CSS
Node.js
Vue.js
Веб-разработка
Progressive Web Apps
PostgreSQL
MySQL
GitHub
С ростом объёма кода отдельного приложения, написанного на JS, перед разработчиками начинают вставать те же проблемы, с которыми сталкивались разработчики на других ЯП в "кровавом энтерпрайзе". Это хорошо, значит всё больше JS-программистов начнёт видеть пользу в методах, применяемых в "кровавом энтерпрайзе" (интерфейсы, внедрение зависмостей и прочий SOLID). Всё то, что не нужно на отдельной веб-страничке, но без чего не строятся кодовые базы на 1+ MLOC.
Смотрите, программы на Java пишутся не так, как программы на PHP. Программы для бэка пишутся не так, как для фронта. С MySQL нужно работать слегка по-другому, чем с PostgreSQL. На реакте пишут не так, как на ангуляре или на вью. А вы призываете меня смотреть на "проблему" не "через мои привычки", а "объективно"? :)))
Это вы уловили.
А вот этот уровень пока не перешагнули.
Вы не понимаете того, что я написал в своей публикации и для чего я это написал. Более того, вы не пытаетесь понять. Вы даже эксперимент с
setupFilesвvitestне поставили.Я вас уверяю - лично вам DI в JS не нужен. Если кто-то будет пытаться вам навязать внедрение зависимостей в JavaScript, то просто дайте ему ссылку на вот эту нашу переписку.
Продолжайте программировать так, как вам удобно. Я тоже так делаю. Мы все так делаем. И это - не проблема :)
Кстати, я тут подумал, что я в своём JS-коде сейчас вообще нигде не использую `extends`. У меня как раз композиция вместо наследования.
Можно попытаться, но получится плохо. На прототипах строятся, скорее, абстрактные классы, а не интерфейсы. Вот тут прочитайте про интерфейсы в разделе "2. Первая ось: универсалии, или где живёт общее". Текст не мой, но очень интересный. Он объясняет, почему после стольких лет доминирования наследования в ООП появилась композиция.
Проблемы отличия абстрактного класа от интерфейса возникают при множественном наследовании. На малых кодовых базах их не будет. А в Magento были и я их лично щупал - как совместить в одной кодовой базе 3-4 плагина сторонних разработчиков, которые друг про друга не знают, но которые по-своему переопределяют один и тот же базовый функционал платформы? Причём в Magento для этого и инструменты были, и даже переопределялось, и даже работало в проде потом.
Но если вы сделаете DI на прототипах, я с интересом ознакомлюсь.
Это не monkey patching :) Вы сейчас притянули целую библиотеку - vitest, у которой более 20 зависимостей. Вполне возможно, что "под капотом" там используется monkey patching, но вот вы сами - используете библиотеку. Так же, как и я. Только я использую свою библиотеку
teqfw/di(у которой 0 зависимостей) для сбор прода, а вы используете vitest (с 20 прямыми зависимостями) для тестов.Для исходного кода в DI-стиле:
Тестовый код выглядит так:
Без каких-либо фреймворков - простой JS на проде, простой JS в тестах. Всё в рамках обычного nodejs. Это - DI в самом его рафинированном виде.
Вы можете таким образом весь проект разбить на es6-модули без единого импорта, указывая зависимости в аргументах фабричной функции (конструктора), а затем в одном головном файле статически импортировать все модули и вручную собрать тот граф зависимостей, который вам нужен. Так никто не делает, но можно.
Своим vitest вы просто обернули и скрыли сложность тестирования. Что будет, если сделать import
./storage.jsдо того, как сделатьvi.mock('./storage.js'...)? Например, вsetupFiles? Статические импорты - они такие.И вы всё ещё пока не ответили про интерфейсы и cross-cutting функциональность. Про интерфейсы я за вас отвечу - вы их не используете, потому что в JavaScript интерфейсов нет. И cross-cutting вы тоже не используете, т.к. у вас нет единой точки создания объектов, где вы могли бы их модифицировать (в TS есть транспиляция и аннотации, а в JS - нет). А ещё у меня из контейнера все объекты выходят “замороженными” и monkey patching на них не работает. Вам даже не понять, для чего нужна может быть такая функциональность. Вы из другого мира - не из мира Java/PHP.
Проблема в том, что Java и PHP в браузере не работают - только JS. Вот поэтому я, как джавист, “коверкаю и тащу магию в продуктовый код” :) Мне удобнее писать JS-код так. Мне и моим ИИ-агентам.
Покажите, пожалуйста, код, как это делается. Я пришёл в JS из мира Java & PHP. Возможно, я просто не знаю каких-то базовых основ или знаю их по-другому.
Я использую интерфейсы в своём JS-коде без транспиляции из TS (или любого другого ЯП) путём подмены токена интерфейса токеном имплементации в настройках DI-контейнера (препроцессор). Как вы используете интерфейсы в JS?
Я использую Proxy для добавления cross-cutting функциональности в свои приложения на JS. Через post-процессор я могу обернуть любой объект, который используется в качестве зависимости, включая библиотеки nodejs. Как вы это делаете в JS-коде без транспиляции из TS (или любого другого ЯП)?
Не сомневаюсь. Дело не в этом. Смысл DI - в эволюции кода. Одноразовые программы можно писать сразу в машкодах, если у вас к ним способности.
Вы считаете, что DI нет смысла использовать в несерьёзных проектах?
Я попытался вам показать то место в книге, где говорится, что "интерфейс зарождается в коде потребителя. Это справедливо для любого ЯП." Похоже, у меня не получилось :)
С интересом прочитаю вашу точку зрения. Даже подпишусь, чтоб не пропустить :)
Ведь это и есть классическая формулировка ISP по Мартину, разве нет?
Проекты на JS или на TS? Судя по "Реально в js нет интерфейсов... ну это надо как-то проверять тогда в коде чтоли...", не на JS.
А я использую инверсию зависимостей в своих проектах именно на JS. Не на TS, не на Java, не на PHP, а именно на "ванильном" ES6+. И эта инверсия у меня такая, какая есть, именно потому, что это JS, а не TS.
Вот классическая формулировка Роберта Мартина:
О направлении требований говорит "Clients should not be forced to depend...". Клиенты (потребители) не должны зависить от неиспользуемых ими интерфейсов.
Это значит, что клиенты сами определяют свою зависимость от интерфейсов, которые им нужны (that they use). Независимо ни от кого. И это уже дело среды исполнения обеспечить поставку таких компонентов, которые нужные клиенту интерфейсы имплементируют. При этом реальные возможности этих компонентов потребителя (клиента), за границами его нужд, не волнуют совершенно. Это может быть God Object, может быть "оркестр", совмещающий несколько интерфейсов, а может быть единичная имплементация именно этого интерфейса. As you wish, как говорится.
Требования к интерфейсу задаётся в местах его использования, а не в местах его имплементации.
Сформулируйте, пожалуйста, ISP в том виде, который вы используете. Я покажу, что говорит о направлении требований.
Системный аналитик хорошо представляет откуда берутся требования к вашему коду.
Посмотрите, что такое "I" в аббревиатуре "SOLID" (Interface Segregation Principle). Он как раз об этом - требования идут от потребителя, а не от возможностей поставщика.
А вы попробуйте обсудить эту точку зрения с вашим системным аналитиком :)
Да. Но это в тех языках, где "интерфейс" поддерживается на уровне языка. В JS такого нет.
Именно так. Вы исходите из нужд конечного пользователя приложения (потребителя) и выстраиваете весь домен вокруг этих нужд, пытаясь их "угадать" / "вычислить" / "опросить". В коде точно так же. Успешность вашего интерфейса зависит от того, насколько точно вы представляете нужды вашего потребителя.
Полностью согласен с такой постановкой вопроса. Не человек должен сверять код с кодом, а специальные программы. Поэтому, когда я перешёл с PhpStorm на VSCode, у меня просто волосы дыбом встали от того, насколько плохо поддерживается JSDoc в VSCode. В IDEA (WebStorm, PhpStorm, ...) IDE самостоятельно связывает JSDoc-аннотации
@interfaceи@implementsв JS-коде и обеспечивает навигацию по коду, его проверку и автокомплит. И WebStorm умел это делать ещё тогда, когда TypeScirpt'а ещё не существовало.Чтобы получить аналогичное поведение в VSCode, нужно постараться. Но это и понятно, Microsoft изо всех сил развивает свои активы (TypeScript и VSCode).
tsserverне будет поддерживать JSDoc в полной мере.Все репозитории должны знать контракт, который ожидается любой функцией, использующей эти репозитории. Можно размазать контракт по множеству всех функций проекта, а можно вынести контракт в отдельный документ и по нему сверять - как ожидания функций-потребителей, так и возможности имплементаций.
В TS есть возможность описать
interfaceв отдельном исходнике, который при транспиляции не превращается в js-код, из-за того, что в JS нет интерфейсов вообще.Зато в JS есть JSDoc и возможность описать интерфейс как в отдельном файле, так и в
types.d.ts(если в пакете интерфейсов не много).Я в примере обращал ваше внимание, что интерфейс зарождается в коде потребителя. Это справедливо для любого ЯП.
Возможно это ставит с ног на голову привычные представления об интерфейсах, но если пристально подумать...
А так вам просто нужно место (файл), где описать какой-то интерфейс в том или ином виде, предусмотренным соответствующим ЯП, или в md- или txt-файле, если этот ЯП ещё менее развит, чем JS с его JSDoc.
Совершенно верно. Но и для TypeScript это точно также нужно будет искать и менять. И для любого друго ЯП тоже. Если вы замкнулись на конкретную имплементацию, а не на интерфейс.
Но вы можете сделать "фейковый интерфейс" в JS (вот работающий пример):
и две имплементации:
и
А в зависимостях компонентов указывать именно интерфейс
Всё, как в других ЯП.
Да, в Composition Root нужно будет решать, какая из имплементаций интерфейса в какой компонент инжектится ("везде одна и та же" или "в эти - одна, в те - другая"). Но такое решение нужно принимать везде, где указан интерфейс вместо имплементации. Я ж говорю, всё, как в других ЯП.
"Это всё" слишком широкое понятие. А излишняя самоуверенность есть признак узкого кругозора.
Да, конечно. Это в любом ЯП так будет. Если вы в runtime умудритесь подсунуть зависимость не с тем контрактом, завалится всё приложение, если не стоит обработки ошибок. Другое дело, что многие ЯП заставляют разрабов описывать контракты и проверяют соответствие контрактов на этапе компиляции - это уменьшает вероятность получить нечто неожиданное в runtime.
Проверять не надо - неэффективно. Достаточно обеспечить поставку зависимости с соответствующим контрактом и обработку ошибок на нужном уровне приложения.
Так оно и было. Всё остальное наросло сильно позже.
Я начинал программировать на PHP, когда там ещё не было возможности указывать типы в сигнатуре функции (до PHP 5.0, 2004 год):
Очень похоже на нынешний JS, не правда ли?
После 5.0 стало можно писать так:
А после PHP 7.0 (2015) так:
Я уверен, что если бы не Microsoft с TypeScript, то и в JS уже давно была бы такая возможность. Но имеем, что имеем. В JS в 2026-м году нет ни интерфейсов, ни возможности указать типы в сигнатуре функций.
Поэтому вот - описываем контракты в коде. Как в PHP четвертьвековой давности.
А если нет, то - что?
Компонент ожидает, что "repository имеет функцию findNameById" - это его право. В данном случае ожидания выражены через код. Можете добавить JSDoc'и, если хотите подробностей. Мы сейчас про JS говорим, тут - вот так. В других языках контракты (ожидания) описываются по-другому.