Pull to refresh

Comments 22

Подождите… правильно ли я понимаю что вся соль в отказе от явных импортов модулей в пользу… глобального к ним доступа? Так это же не DI.


Честный DI — это когда мы можем его выкинуть (например заменить на фабрики) и это не повлечет за собой переписывание нашего кода. И никакого отношения к модулям это не имеет. То есть применение DI в контексте javascript должно быть ограничено управлением зависимостями объектов, а не управление зависимостями на уровне модулей.

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


Честный DI — это когда мы можем его выкинуть (например заменить на фабрики) и это не повлечет за собой переписывание нашего кода.

Это удастся только в том случае, если интерфейс загрузчика и DI идентичны. Приведите пример.

если интерфейс загрузчика и DI идентичны

Повторюсь. Код ничего не должен знать о существования DI. Когда у нас есть "загрузчик" — это называется service locator а не DI. Именно по этой причине DI работает на уровне объектов, а не модулей. По сути в случае DI у нас все зависимости должны приходить сверху, что происходит и в случае с модулями. Мы можем открыть файлик, и сверху увидеть все зависимости.

Судя по тому, что я вижу, это больше похоже на некий совершенно обыкновенный автолоадинг, как в пыхе (ну и с натяжкой в джаве), нежели на сервис локатор, т.к. "модули" грузятся по мере необходимости по определённому правилу подгрузки, а не определены заранее с определённым поведением этой подгрузки (если я правильно и точно понял что всё же такое сервис локатор).

Здорово что так можно, но это самый настоящий антипаттерн

1. Скрывать от программиста объявление импортов (или require) плохо. Возникает ощущение «магии» и члены команды, особенно новые могут перестать понимать что именно происходит и откуда ноги растут — что вынуждает их держать в голове еще больше правил о том что и как у вас в проекте работает. Это ведет к ошибкам в рантайме. Код должен быть как можно более explicit, говорить сам за себя.
2. with крайне не рекомендуется и запрещен в 'strict mode', т.е. достаточно давно. Одна из причин — сложность или невозможность некоторых JIT оптимизировать такие функции. Возможно в v8 это пофиксили, но не уверен что они тратили на это силы, так как with не рекомендован и собирался быть переведенным в deprecated
3. Уходит возможность навигации в проекте по модулям в IDE, возможно во всех IDE

Все эти причины также относятся например к алиасам webpack'a

Во многом согласен, но как я написал во втором абзаце, цель исследование возможностей Node.js.


  1. Это решается соглашениями об именовании и выносится в документацию. Легко устраняется именованием переменных в определенном стиле.
  2. Это не фиксили и об этом я написал в конце.
  3. Соглашусь, но IDE для JS в большинстве своем не понимают все что касается рантайма и поэтому далеко не всегда находят нужный код даже в достаточно простом проекте. Мой опыт в основном касается WebStorm'а, который достаточно хорош и все же не всегда помогает. При этом я предлагаю достаточно явное решение – использовать scope.js, для которого можно добавить нужный плагин в IDE.

Данный код – это пример проектирования по соглашению, т.е. когда какое-то решение принимается разработчиками с учетом его минусов и плюсов. И это хорошо подходит для прикладных программ, для которых более критична скорость и удобство разработки. Это как денормализация БД.

Есть вариант и без скрытия импортов: www.npmjs.com/package/packagerify
Из бонусов: автокомплит в Идее (ну и ВебШторме, разумеется) и возможность пробежаться в цикле по модулям в пакете (удобно при создании разных загрузчиков)
> ну и, конечно, ускоряет загрузку.

А скорость работы вы тестировали?

> v8 не оптимизирует код внутри with

Уже неверно. TurboFan оптимизирует функции с with. А до TurboFan — не «внутри with», а «содержащий with».

И да, мне не очень нравится идея — так сложнее отслеживать зависимости модулей, хуже ясно что чего использует, плохо видна структура проекта.

Да, это можно исправить «соглашениями об именовании», но эти соглашения об именовании в таком случае должны в себя включать перечисление используемых модулей в шапке файла. То есть как раз то, от чего вы и избавились.
А скорость работы вы тестировали?

В проекте, который переписал время инициализации сократилось с секунд до долей секунды. Но это зависит от структуры проекта.


TurboFan оптимизирует функции с with.

Пофиксил


И да, мне не очень нравится идея — так сложнее отслеживать зависимости модулей, хуже ясно что чего использует, плохо видна структура проекта.

Практика показала, что особых трудностей это не вызывает. Если UserService всегда будет находиться в файле services/user-service.js, то это сильно упрощает работу. Использование простых имен, безусловно, может повлечь за собой путаницу.


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

А как у вас обстоят дела с производительностью? Я как-то решил поковыряться с vm.runInThisContext и прочими методами, пытаясь, без серьёзных препроцессоров, вроде babel, обеспечить работу будущего export-import. Ага, временно написав свой собственный :) Побаловавшись с vm, я добился некой минимальной работоспособности, но первые же замеры скорости убили всю малину. Разница в 2 порядка. Нет нет, не в 2 раза, а в 2 порядка. Порыскав в сети что с этим можно сделать, я ничего не нашёл. Идею забросил, всё равно спецификация пока не готова.


Посмотрел ваш код на github и вижу всё тоже самое: ручное считывания содержимого файла и vm.runInThisContext. А что изменилось в nodeJS 6 в этом плане? Разве это не завелось бы с тем же успехом и на nodeJS 4, скажем?


Сама же идея такого вот DI мне кажется ну очень сомнительной. Но больше интересует — производили ли вы замеры по производительности?

Node.js использует runInThisContext, так что ваша проблема где-то в другом месте.
В шестой версии появились Proxy из коробки, раньше вам бы пришлось вызывать такой код с флагом --harmony_proxies.
Тесты производительности не показали существенной разницы, но я тестировал достаточно простой код. Наверное, стоит сделать репозиторий с бенчмарком.

Node.js использует runInThisContext

Я полагаю, что nodeJS имеет некий кеш уже ранее распарсенных файлов, и не парсит их повторно, если размер или mtime не отличается. Не проверял правда. А вот как заставить оный заработать в случае такого вот хака мне не понятно. Я искал методы чтобы самому заняться кешированием AST или чего-нибудь такого, но ничего не нашёл.

Каждый файл парсится и исполняется только один раз. В моей реализации тоже есть кеш.

А мы точно имеем ввиду один и тот же кеш? Я имею ввиду не runtime, а, как бы это назвать, ну пусть будет "обще-системный". Т.е. 1-й запуск приложения, положим, 2sec, а 2-ой 50ms.

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

А не пробовали использовать vm.runInNewContext() вместо with? Интересно, прокси будет продолжать работать?

Не пробовал. Вообще Proxy должен продолжить свою работу, если нет, то, по-моему, это баг.

Когда я писал сервисы с использованием Node.JS, через некоторое время осознал, что DI уже встроен в Node.JS. Посудите сами:

1. Нужно на момент запуска приложения решить, какую реализацию выбрать? Не проблема — делаем proxy js'ник, который вернет нужную реализацию, например, на основе конфига.
2. Нужно покрыть модуль юнит-тестами? Не проблема — мокаем с помощью proxyquire.

require по своей природе является Service Locator.

Исследование провёл хорошее, изложил всё грамотно и последовательно. А вот сама идея хоть и понятна но попахивает…

Согласен. Намного интереснее было бы организовать ленивую инициализацию модулей явно. Мол require бы вернул проксю, и модуль реально загрузился бы только в момент когда он реально нужен.


Так у нас все зависимости будут декларироваться явно, и при этом мы получаем тот же профит.

Sign up to leave a comment.

Articles