Pull to refresh

Comments 84

В целом, соглашусь с тезисом статьи, пространства имен – это хорошо и полезно. Но хочу немного уточнить некоторые фактические моменты:


В результате, в Java можно независимо создать большое количество бесконфликтных пакетов (модулей) без явного согласования их наименования независимыми группами разработчиков, а в JS для этого нужно явно использовать реестр npm

Npm использовать необязательно. Вы можете опубликовать пакет на Github/Gitlab и добавить себе его в зависимости


"my-package": "github:username/repo"

Полный список поддерживаемых форматов есть в документации


Еще можно поднять свой собственный репозиторий и публиковать пакеты в него. Если в компании куплена Artifactory Pro, то там это идет из коробки


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


IDE способно без труда сформировать этот адрес. В PhpStorm это делается так [...] Тот же PhpStorm теряется, если применить подобный приём для JS-кода

Так это недостающая фича в PhpStorm, а не принципиальный косяк npm. Ничто не мешает генерировать ссылку типа такой: npm-module-name/path/to/file.js:123.
В личной практике мне никогда это особо не мешало, ссылки на исходники коллегам скидываю на Github/Gitlab/Bitbuket и т.д.


Кстати, наличие классов в ES6 и отсутствие пространства имён в смысле логической группировки кода вероятно приведёт к появлению в больших ES6-проектах имён, аналогичных именам в Zend1 (Module_Path_To_Class).

Вот этот вывод непонятно как получился. Имена классов в JS-модулях локальные, придумывать уникальные имена в рамках всего проекта не надо.

Спасибо за развёрнутый коммент, коллега.


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

Вот за то и речь. Называя как угодно свои приватные модули, мы уменьшаем себе возможность их использования в глобальных проектах. Допустим, вы сделали приватный модуль debug для своего проекта, выложили на корпоративный репозиторий и используете его в своих проектах. В какой-то момент ваша корпорация поглощает другую корпорацию и вашему IT-отделу ставится задача адаптировать их замечательные наработки в ваши. А у них такой же модуль debug используется. Вот и получаем вилы на ровном месте.


В java-мире, например, корпорация Oracle поглотила корпорацию Sun, а пакет com.oracle.json замечательно сосуществуют рядом с пакетом com.sun.net.httpserver.


А представьте, что вы бы захотели опубликовать свой приватный пакет debug для открытого использования?


Ничто не мешает генерировать ссылку типа такой: npm-module-name/path/to/file.js:123.

Это всё равно привязка к тексту в файле, а не к логической структуре кода (константа, метод, класс, ...). Адресация поплывет при изменении кол-ва строк в файле.


Имена классов в JS-модулях локальные, придумывать уникальные имена в рамках всего проекта не надо.

Вполне возможно, что мой прогноз — "пальцем в небо".

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

Значит я не так понял исходное утверждение. То есть плохо то, что npm-модулям любят давать короткие имена…


Ну во-первых, в npm завезли scopes, свои служебные пакеты вы можете называть @mycompany/package, то есть ситуация как минимум не хуже composer.


Во-вторых, чем длиннее имя, тем сложнее его запомнить. Понятно, что org.apache.commons помнят все, но вот какой-нибудь com.squareup.retrofit2 или io.reactivex.rxjava2 вряд ли, все равно будете гуглить сначала по названию, retrofit или rxjava соответственно, чтобы узнать полное имя.

Понятно, что org.apache.commons помнят все, но вот какой-нибудь com.squareup.retrofit2 или io.reactivex.rxjava2 вряд ли

Современные IDE довольно умны и подобная проблема в принципе не возникает. Достаточно приблизительно помнить «имя» необходимого пакета, а корректный импорт IDE берет на себя.

Хорошо, решение есть. Но посмотрите на ситуацию со стороны: сначала придумали длинные уникальные имена, а потом потребовались утилиты, чтобы их не писать. Логично!


То есть получается, что у любой библиотеки все равно есть имя собственное, а приписанное сначала com.mycompany особой роли не играет

Похоже, я ввёл вас в заблуждение, пытаясь продемонстрировать работу "пространства имён" на примере библиотек.


namespace в PHP и package в Java относятся к исходному коду, а не к библиотекам.


Попробую запутать вас ещё больше. Всем известные интернетовские домены — это пример применения namespace'ов на практике. Две разные библиотеки два разных домена с именем mail одновременно существуют в одном проекте интернете именно потому, что они принадлежат двум разным пространствам имён:



Пространство имён — некоторое множество… созданное для логической группировки уникальных идентификаторов…


На самом деле можно в интернете проекте обойтись без доменов верхнего уровня и вообще без доменов namespace'ов и адресовать все хосты библиотеки "плоской" строкой. Это примерно то же самое, как хранить все файлы на компьютере в одном корневом каталоге. Да-да, файловая система — это тоже пример "логической группировки уникальных идентификаторов" (имён файлов) при помощи "некоторых множеств" (каталогов).


И если на компьютере хранить 5-10 файлов, то вполне резонно задаваться вопросом, а для чего мне вообще нужны каталоги? И получить вполне логичный ответ — для хранения 5-10 файлов каталоги не нужны.

Дело в том, что у нас уже есть файловая система, и модули уже разложены по папкам и файлам. Зачем дополнительно вводить абстрактное пространство имен, если все и так организовано?

В вашем проекте папки и файлы организованы так, в моём — по-другому, у кого-то — по третьему. Посмотрите содержимое файла autoload_namespaces.php, фрагмент которого я привёл в разделе "Автозагрузка кода". В нём видно, как файловая система каждого модуля (а сторонний модуль в вашем проекте с вашими правилами для кого-то является собственным проектом со своими правилами) монтируется в единую логическую структуру кода в вашем проекте. Вот, например, исходники модулей находятся в таких подкаталогах внутри каталога ./vendor вашего проекта:


  • ./magento/zendframework1/library
  • ./phpspec/prophecy/src
  • ./phpmd/phpmd/src/main/php

Но вы не заморачиваетесь путями к исходникам, а просто пишете в своём коде:


$center = new \Prophecy\Call\CallCenter();

Поиск файла с исходниками автозагрузчик autoload.php выполнит самостоятельно, на основании привязки пространства \Prophecy\* к каталогу ./vendor/phpspec/prophecy/src.


Если ваш проект собран при помощи PHP Composer, то ваш код не изменится, если Konstantin Kudryashov (автор модуля phpspec/prophecy) захочет изменить структуру своего модуля и перенести исходники в своём модуле в каталог, например, ./phpspec/prophecy/src/main/php. Он просто укажет в composer.json своего модуля:


    "autoload": {
        "psr-0": {
            "Prophecy\\": "src/main/php"
        }
    }

и при сборке или обновлении вашего проекта в следующий раз в карту пространства имен autoload_namespaces.php запишется так:


'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/main/php'),

С появлением autoloader'a PHP-разработчики перестали держать в голове файловую структуру своего проекта (и файловую структуру сторонних модулей, используемых в своём проекте) и стали держать логическую структуру кода (которая гораздо проще).


Пространство имён не нужно, если на компьютере в проекте хранится участвует 5-10 файлов разработчиков со своими модулями. Но если на компьютере в проекте хранится участвует 1000 файлов разработчиков со своими модулями, то пространство имён становится более актуальным.

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

Отсюда делаю вывод, что эта фича не является обязательной для достижения «максимальной сложности приложений».

Пространства имён не привязаны к языку. В Java нет жестких требований по расположению файлов — вы указываете их местоположение в CLASSPATH при запуске приложения.


И пространства имён не являются обязательной фичей для достижения "максимальной сложности приложений". Я пытался донести мысль, что пространство имён при прочих равных условиях (например, язык и квалификация разработчика) позволяют создавать более сложные приложения, чем без них. Вы можете писать на PHP без использования namespace, некоторые до сих пор так и делают. Но в таких пакетах, как Zend2, Symfony, Laravel — используются namespaces.


Кстати, в Yii и CodeIgniter, к моему удивлению, не используют пространства имён. В результате в Yii имена файлов выглядят так:


image


А в CodeIgniter так:


image


Обратите внимание на префиксы в названиях файлов — разрабы вынуждены обеспечивать уникальность имени каждого файла в пределах проекта (по сути, вручную, не используя средств языка, вводят пространства CDb и DB_). Да, для 1000 файлов они выкрутятся (какой ценой?), а для 300 млн.?


Если вдруг решите попрограммировать на PHP, не начинайте с Yii и CodeIgniter ;)

Первый Yii — это ныне мёртвая версия фреймворка 2008го года, тогда о неймспейсах не то что не слышали, но даже и не задумывались, а на дворе был PHP ~5.1. Смотрите лучше на Yii2 или Yii3.
Да, для 1000 файлов они выкрутятся (какой ценой?), а для 300 млн.?

Это как раз для node.js не проблема. С большим количеством файлов их модульная система хорошо справляется. Настолько хорошо, что некоторые разработчики создают отдельные модули для самой тривиальной функциональности, порождая поводы для мемов.

Затем, что это совершенно разные вещи.
В автобусном парке есть два икаруса и один лиаз с номерами Н123С, Р778Т и М543Н.
С другой стороны, автобусный парк обслуживает маршруты 711, 215 и 987.
Даже не смотря на то, что мы можем уверенно сказать, что икарус Н123С обслуживает маршрут 711 — это все равно две разные, не связные между собой, плоскости абстракций.
Если следовать вашей аналогии, то можно сказать, что неймспейс и путь до файла с классом вообще никак не похожи. Но разве класс `\Prophecy\Call\CallCenter()` может внезапно смаппиться на файловую систему как `bla/bla/bla`?

Сможет. Если прописать в ./vendor/composer/autoload_classmap.php


'Prophecy\\Call\\CallCenter' => $vendorDir . '/bla/bla/bla.php',

Ээээ… Вы серьёзно предлагаете что-то трогать в папочке вендор? Или это просто для примера? Если для примера, то надо было что-то такое приводить в пример:


{
    "autoload": {
        "classmap": ["./bla/bla"]
    }
}

Через composer.json оно, конечно, лучше, но я не стал плодить сущности в примере :) Тем более, что в конечном итоге оно всё равно окажется в ./vendor/composer/autoload_classmap.php. А так ваше замечание совершенно справедливо, в папке ./vendor/composer лучше не делать изменения ручками.

Тем более, что в конечном итоге оно всё равно окажется в ./vendor/composer/autoload_classmap.php


А вот и не факт. Ещё есть APCu и авторитативные карты классов.

А так ваше замечание совершенно справедливо, в папке ./vendor/composer лучше не делать изменения ручками.


Не то что «лучше»… А никогда и ни при каких обстоятельствах этого не делать. А если кто тронет — за это трогалку отчекрыживать.

Ну разве что только для отладки кода какого-нибудь можно трогать.
максимальная сложность приложений, которые можно создать на Java/PHP/C++/..., не может быть достигнута разработчиками с аналогичной квалификацией на JavaScript/Python/C/....


Абсолютно недоказанное утверждение. Никому отсутствие namespace не мешает говнокодить огромные приложения на javascript. В JS область видимости очень ограничена модулями. Два модуля с одним именем, ну не знаю, кому это может быть нужно.
Сколько пишу на JS никогда не сталкивался с тем, что мне нужны были namespacе.
Один контрпример:
— ядро linux написано на C и там нет неймспейсов;
— в С++ они есть, поэтому запас по сложности у него выше;
Ну-ка, покажите хоть одно ядро ОС на С++ сравнимой сложности с Linux? А? Где оно?
P.S>
( я прекрасно знаю области применения обоих языков, это шутка!)

А еще не рассказали про самое главное неудобство при работе с Javascript-модулями. Файлы из своего проекта можно импортировать только через относительные пути. Из-за этого получаются ужасные цепочки. Реальный код:


import UserService from '../../../../src/services/user/UserService.js';

Стандартной возможности написать импорт относительно корня проекта нет. Есть разного рода костыли (34 штуки!), но поскольку они нестандартные, то с ними регулярно что-то отваливается.


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

Не поможет. Typescript же не перезапишет пути, в скомпилированном коде они так и останутся, и все равно придется использовать один из тех воркэраундов.
Typescript же не перезапишет пути

При транспиляции он «развернет» их в относительные. Или я вас нe понял?
Я проверил и — таки нет, justboris тут прав. Я довольно давно настраивал билд у себя и у меня этот резолюшен в транспилированном коде делался через вебпак.

Вот issue на этот счет для typescript, и это не собираются чинить: github.com/Microsoft/TypeScript/issues/10866.
Решает, но нет единообразного решения. Сборщик, тест-раннер, typescript — всем нужно указывать путь своим особым способом.

В нормальной системе это должно указываться в одном месте, чтобы все инструменты подхватывали автоматически. NODE_PATH мог бы быть таким стандартом, но его намеренно выпиливают из реализации ES-модулей в Node: nodejs.org/api/esm.html#esm_no_node_path
Файлы из своего проекта можно импортировать только через относительные пути.

Этот коммент замечательно иллюстрирует сказанное в пункте "Автозагрузка кода". Нам приходится отталкиваться от файловой структуры, если нам сложно отталкиваться от структуры кода.

Ну так в JS в этом планe просто «другая парадигма»
Использование namespace’ов в PHP позволяет легко обнаруживать подобные конфликты на уровне IDE (для примера я сделал второй файл с дубликатом класса внутри):

Ваш пример не имеет особого смысла в Node, т.к. там декларации классов не попадают в глобальный скоуп. Т.е. можно в двух соседних файлах объявить класс с одинаковым названием и это не будет ошибкой.

Вы правильно сказали — "не попадают в глобальный скоуп". В JS вообще нет namespace'ов в смысле логической группировки элементов кода. Есть scope. Именно поэтому на JS'е сложно писать сложные проекты — сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены. А в PHP можно. И в Java можно. И в Typescript можно (если я правильно понял).

сложно уникально адресовать два класса с одинаковым названием

Вы можете привести пример из жизни (не сферический) где такое встречается и именно namespace'ы выручают?

Я не программирую на TypeScript, а в JS нет namespace'ов (поэтому они не выручат). Что касается PHP, то вот два класса с одинаковым именем Configuration, конфликт которых "разводят" namespace'ы (оба класса, что характерно, находятся в файлах Configuration.php):



Для того, чтобы создать объекты этих классов в современном PHP-приложении мне достаточно знать их полные имена, все остальное делает менеджер зависимостей (autoloading).

Но в JSe вам достаточно просто сделать
import { Configuration as dbalConfig } from 'doctrine/DBAL'
import { Configuration as ormConfig } from 'doctrine/ORM'

То есть там в принципе не стоит такой проблемы.

Что такое doctrine/DBAL и doctrine/ORM? Каталог, файл или некоторая строковая метка для поиска пути к исходникам по некоторому алгоритму?

По сути — это модуль.
Каталог, файл или некоторая строковая метка для поиска пути к исходникам по некоторому алгоритму?

А какая разница? Если при наборе from 'doctrine/...` вам ide так же выдаст список доступных импортов.
Стиль кода будет немного иной, понятно что в JS так исторически сложилось что модульность там прикручена сверху уже сложившейся платформы, но при написании кода это почти никогда мешает. Да, на локальные модули приходится relative path писать, но как вы написали есть 100500 способов это разрулить.

Я сейчас как раз в основном пишу на JS и Python, и я там ни разу не сталкивался с проблемами в стиле «мне нужно импортировать два класса с одинаковым названием и это не решается алиасами»

Вы по ходу дела плавно подменили первоначальную проблему "сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены" другой — "мне нужно импортировать два класса с одинаковым названием и это не решается алиасами" :)


Дело в том, что в PHP-проекте я могу написать:


$init = new \Doctrine\DBAL\Event\Listeners\MysqlSessionInit()

без того, чтобы использовать import или require. При этом я не должен знать о том, в каком каталоге находится файл, в котором находится исходный код. Именно потому, что в PHP/Java/TypeScript/… есть директива namespace. А в JS/Python этой директивы нет, вы её никогда не использовали и не понимаете для чего она вообще может быть нужна. Если вам она не нужна, то это говорит только о том, что вы ещё не достигли своего предела сложности в JS/Python.

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

А в чем разница? В моем примере выше никакой привязки к файлам нету.

При этом я не должен знать о том, в каком каталоге находится файл, в котором находится исходный код. Именно потому, что в PHP/Java/TypeScript/… есть директива namespace. А в JS/Python этой директивы нет, вы её никогда не использовали и не понимаете для чего она вообще может быть нужна. Если вам она не нужна, то это говорит только о том, что вы ещё не достигли своего предела сложности в JS/Python.

Так себе критерий оценки сложности проектов над которыми я работаю. Сложностью можно управлять по-разному, нe обязательно городить здоровенные монолиты с миллиардами классов, где без нэймспэйсов можно потеряться.
Если вам так нравится думать о там как вам namespace'ы позволяют писать крутой код — пожалуйста, но ваши аргументы для вывода статьи не убедительны.

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

Я хотел увидеть, что серьёзных возражений против этого моего вывода не будет.

Вы сами можете придумать пример, который вас бы разубедил?

Могу. Например, в JS/Python любую функцию проекта можно однозначно адресовать вот таким вот образом без привязки к файловой системе и без использования namespace'ов.

Что-то вы тут спорите, а самого главного никто не написал:


import { Configuration as dbalConfig } from 'doctrine/DBAL'

Вот этот вот код — это императивная конструкция. Она говорит "загрузи мне публичный класс из такой-то папочки". А в случае PHP:


use Doctrine\DBAL\Configuration;

Это декларативная конструкция. Которая говорит о том, что все упоминания класса "Configuration" в коде должны ссылаться на "Doctrine\DBAL\Configuration". При этом:
1) Этого класса вообще может не существовать в природе. Код может выглядеть так:


if (class_exists(Configuration::class)) {
    // Класс существует!
}

2) Подгрузка его произойдёт только в момент непосредственного его вызова. Например, лишь при создании объекта. А так никаких подключений не делается.
3) Сам файл может располагаться где угодно (о чём, вы как раз и спорите), например в бинарном архиве phar (почти как jar в джаве). Или в кеше по какому-нибудь пути "/cache/2019_01_04/version1/1hjf8hfuhf34.php", который генерируется каким-нибудь оптимизатором или препроцессором.

Вот, теперь стало понятнее. Только остается вопрос: если есть столько разнообразных способов загрузить себе класс, то как это дебажить в случае неполадок?


Допустим, вы сделали небольшую правку, а приложение упало с сообщением, что не может найти какой-то класс, с вашими изменениями совсем не связанный. Раз привязки к файловой системе нет, где его искать непонятно.


Спрашиваю безо всякого троллинга, мне и правда интересно как такое решается.

Во-первых, в PHP есть стандарты, которые явно требуют для современно кода относительно чёткой иерархии и именования: PSR-4 и более старый PSR-0, но это не значит, что сделать иначе нельзя.

Во-вторых, автолоадинг — это одна единственная функция в коде, которая принимает коллбек, срабатывающий тогда, когда класс не найден. Его задача — подключить файл. Достаточно там поставить точку останова чтобы понять почему не подключается файл.

Ну а когда кода много и не понятно что куда и откуда, то есть IDE. Она умеет индексировать и находить что угодно. А учитывая уникальность имён в системе (здравствуйте неймспейсы) — однозначно указывает на файл. Плюс ещё мощная система рефлексии, где одним методом можно получить физическое раположение файла в ФС по любому имени. По этому, физически сам файл с классом найти можно элементарно.

В-четвёртых, опять же стандарты, на этот раз PSR-1 и PSR-2, помимо кодстайла они содержат и другие вещи, например в PHP на уровне PSR-2 стандарта один класс должен соответствовать одному файлу. А это облегчает поиск файлов. Т.к. именование оных соответсвует имени класса и если какой-то класс не находится, а файл имеет точно такое же имя, то возможно что-то пошло не так не в именовании, а где-то на уровне кеша.

В-пятых, опять же IDE, учитывая количество всякое разное количество стандартов — она орёт благим матом, если где-то что-то написано без оных рекомендаций.


Короче, это даже не проблема, наткнуться на такую ошибку может только новичок. Можно посмотреть на тостер по сабжевому запросу и понять, что решение проблем элементарное: toster.ru/search?q=class+not+found

Понятно. Есть набор правил и ограничений, чтобы пользователи не стреляли себе в ноги, а классы – не терялись.


Но в вопросе import против use это ясности не добавляет.


  1. import/export в JS – это статические конструкции. Они читаются перед исполнением кода модуля, не могут быть вложены в if или функцию, им нельзя динамически сгенерировать имя. Вполне себе декларативное описание, как и use
  2. "module-name" в import с точки зрения спецификации языка – это просто индентификатор, который резолвится на усмотрение исполняющего окружения. В браузерах, например, предлагается использовать карты импортов, которые очень похожи на конфигурацию автолоадера
  3. Ленивой загрузки у JS-модулей нет, import {SomeClass} from 'some-module' перед исполнением проверит, что some-module содержит экспорт SomeClass. Для ленивой загрузки есть асинхронный await import('some-module'), который загрузит модуль динамически.
    Я считаю это плюсом, потому что в случае ошибки вы хотите знать об этом как можно раньше, а в критичных местах можно вставить динамический импорт, прекрасно осознавая риски.

Есть ли еще какие-то киллер-фичи автолоадера, которые я пропустил?

1. Как раз нет. Напишите «import * from 'undefined'» и получите ошибку. Потому что они исполняются во время деклараций. Это и говорит об императивности этих инструкций.
2. Верно, если не принимать во внимание п.1
3. Опять же — этот импорт императивен. Если вы подгружаете полифиллы в PHP, но не испольузете новые функции — они просто не будут подключаться и выполняться. В случае JS весь код подключается всегда, а чтобы избавиться от неиспользуемого — его костыляют через tree-shaking, который работает через жопу.

Помимо этого есть и другая особенность JS — это манки патчинг.
В таком коде:
// file example.js
import * from 'any';

// file example-2.js
import * from 'any';
import example from 'example';


Будет физически вызвано 4 подключения файлов. Где any просто дублируется в первом и втором файле, клонируя себя в локальный скоуп файлов.

В PHP это не так. В аналогичном примере на PHP не будет выполнено ни одного подключения. Т.е. ровно 0 файлов будет использовано (ну разве кроме самого первого используемого). А во время физического использования внешних зависимостей константность и глобальность имён гаратирует то, что никто не перезапишет и не переопределит класс/функцию, а значит и подключений никаких лишних не надо делать, т.к. нужные конструкции уже загружены в виртуальную машину, на них достаточно будет лишь сослаться.

Напишите «import * from 'undefined'» и получите ошибку.

Синтаксиса import * from нет. Видимо здесь имелось в виду import * as something from 'undefined'. Тем не менее все равно непонятно какая ошибка имеется в виду.


В PHP это не так. В аналогичном примере на PHP не будет выполнено ни одного подключения.

Что вы понимаете под словом "подключение"? Если в файле с классом есть какие-то сайд-эффекты (написать что-то в лог, наприимер), они вызовутся только в момент, когда вы сделаете new MyClass()?

Синтаксиса import * from нет. Видимо здесь имелось в виду import * as something from 'undefined'. Тем не менее все равно непонятно какая ошибка имеется в виду.


Очевидно, ошибка отсутствия этого самого 'undefined'

Что вы понимаете под словом «подключение»?


Статическую линковку файлов.

Если в файле с классом есть какие-то сайд-эффекты (написать что-то в лог, наприимер), они вызовутся только в момент, когда вы сделаете new MyClass()?


В PHP с автолоадигом — да.
Очевидно, ошибка отсутствия этого самого 'undefined'

Имя модуля в импорте всегда должно быть статическим, написать import x from foo + 'bar' + baz нельзя. Поэтому там undefined будет только в том случае, если вы действительно захотели импортировать модуль с таким именем.


Автолоадинг интересная штука, но на любителя. Мне нравится, когда потенциально невалидный код падает сразу после запуска, а не где-то в рантайме (возможно, в проде, если тесты ситуацию не отловили).

Автолоадинг интересная штука, но на любителя.

Давайте другими словами попробую объяснить: В современном PHP вообще не надо писать «импортов», они опциональны. Представьте себе JS без единого импорта? Язык сам знает откуда брать код и что подключать когда потребуется. Потому что все имена в ПО за счёт неймспейсов — уникальны, а любые связанные с этим ошибки вылавливаются на уровне статического анализа, т.к. у PHP он на порядки строже получается, как у TypeScript.

Мне нравится, когда потенциально невалидный код падает сразу после запуска, а не где-то в рантайме (возможно, в проде, если тесты ситуацию не отловили).


Эм… Тогда не надо писать на JS, потому что он тоже падает в рантайме, а не во время сборки, а import — это инструкция препроцессора, а не языка, в этом и отличия))) Да и как бы AMD никто не отменял, он тоже может только лишь во время исполнения сказать, что какого-то модуля не существует.

В Typescript пытались продвигать неймспейсы именно так как вы и описываете: http://www.typescriptlang.org/docs/handbook/namespaces.html


Сами исходники Typescript с помощью них сейчас и организованы, но в целом в сообществе такой способ популярности не имеет. Людям больше нравится писать явным образом импорты и экспорты.


import — это инструкция препроцессора, а не языка

Непонятно, почему вы так считаете. Синтаксис описан в спецификации языка, любой движок с поддержкой последней версии стандарта должен их понимать. То, что сейчас исходники прогоняют через babel – это временное решение для совместимости, в долгосрочной перспективе особой роли не играющее.


Тогда не надо писать на JS, потому что он тоже падает в рантайме

Проблемы есть, не спорю. Но строгое поведение импортов это шаг в сторону улучшения ситуации и должен приветствоваться а не наоборот.

Непонятно, почему вы так считаете. Синтаксис описан в спецификации языка, любой движок с поддержкой последней версии стандарта должен их понимать. То, что сейчас исходники прогоняют через babel – это временное решение для совместимости, в долгосрочной перспективе особой роли не играющее.


ECMA и JS — это всё же чуть-чуть разные штуки. А на уровне JS в браузерах как вы себе представляете этот import? А-ля AMD? Т.е. рекурсивно выгружаем весь node_modules в браузер клиенту для загрузки одного скриптика на пару строк? =)

От бабела с билдом всего в один файлик (образно, конечно, без учёта технологии чанков) никуда не деться, ни сейчас, ни через 10 лет.

Проблемы есть, не спорю. Но строгое поведение импортов это шаг в сторону улучшения ситуации и должен приветствоваться а не наоборот.


А мне вот кажется, что загрузка в память того, что реально используется на месте (причём загрузка один раз), вместо всего что может пригодиться в каждом отдельном модуле — на порядок лучше.

P.S. Именно по-этому фреймы на PHP весом в 100+ метров кода — памяти реально пару метров оперативы загребают. А на ноде «хелло ворлд», который меньше 10, а то и 20 метров кушает — написать довольно сложно. Ну я образно, конечно.
А на уровне JS в браузерах как вы себе представляете этот import

Так импорты уже поддерживаются в браузерах. Вот маленькое демо, например:
https://es-modules-demo.glitch.me


При локальной разработке задержки на загрузку модулей минимальные, поэтому можно грузить сразу из исходников, а не настраивать dev-server, а для продакшена можно и бандл собрать


А на ноде «хелло ворлд», который меньше 10, а то и 20 метров кушает — написать довольно сложно

Не думаю что причина в загрузке модулей. Память больше поедают инстансы классов, а не их конструкторы.
Конечно, всегда есть шансы, что при инициализации модуля запустится рекурсивный скрипт или биткоин-майнер, но мы же только адекватные сценарии рассматриваем?

Так импорты уже поддерживаются в браузерах. Вот маленькое демо, например:

Посмотрите на вкладку network. Такое в продакшене нельзя оставлять по очевидным причинам. О чём я и говорил.


При это предлагаю написать:


function example() {
    import some from "any";
}

Т.е. ленивые (опциональные) зависимости написать невозможно. В отличии от другого языка (не будем показывать пальцем) =)


Не думаю что причина в загрузке модулей.

т.е. считаете что неиспользуемый import почти не кушает памяти?

Посмотрите на вкладку network. Такое в продакшене нельзя оставлять по очевидным причинам.

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


Вот такой код для ленивой загрузки сработает:


async function example() {
    const some = await import("any");
}

А если сделать всю загрузку ленивой по умолчанию, то это плохо скажется на UX. Пользователи хотят чтобы кнопки сразу реагировали на их нажатие, а не ходили на сервер за новым кусочком кода для их обработки. Ленивая загрузка – полезная вещь, но должна использоваться точечно и с умом (по крайней мере, во фронтенде).


т.е. считаете что неиспользуемый import почти не кушает памяти?

Да, вот потребление памяти Gmail (самый прожорливый сервис, из мне известных)



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


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

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

не нарушая семантики языка.

О да, конечно, 404 ошибка из промиза фетча, которую даже через трай кетч нормально не отловить в рантайме (потому что оператор импорта нельзя обернуть в трай/кетч). Против бабеловской ошибки в линкере в статическом связывании на уровне препроцессинга. Это совсем не нарушение семантики =)

Вот такой код для ленивой загрузки сработает:

Потому что это функция, а не оператор. Логично. Которая опять же зависит от путей к файлам на сервере.

А вообще, пока вы не попробуете сами автолоадинг в PHP — не поймёте насколько в JS это всё убого сделано. Но да, это лучше чем было в ES5, где commonjs полифиллами доставлялся.

P.S. А вообще реально попробуйте пописать что-нибудь на PHP. Если говорить о модульности и способах резолва зависимостей — это как небо и земля. Конкурировать тут с PHP может разве что только Java с classpath, но у неё тупо гибкости не хватает.
Вот только нормальной модульности в пхп как раз и нет.

И если в js на уровне модуля можно определить какие вещи мы эспортируем и можно сделать приватные для модуля классы, то в пхп в любом месте можно использовать любой класс.
можно сделать приватные для модуля классы

Вот с этим согласен, это приятная плюшка, которую было бы неплохо иметь и в PHP. Но эта проблема решается на уровне conventions в проекте.

упс
The implementation stalled after much progress and is currently blocked by namespaces existing only-during compile-time and not being available for run-time checks

Ссылок на, хотя бы, минимальную реализацию, найти не получилось.
Implementation
After the project is implemented, this section should contain
is currently blocked by...

Печаль :( Значит, будем выкручиваться через соглашения на уровне проектов. В Magento 2, походу, для этого в модуле оформляется отдельный фолдер ./Api/, в который выкладываются интерфейсы, публичные с точки зрения модуля. Т.е., сторонние пользователи функционала, предоставляемого модулем, должны использовать только то, что находится в этом фолдере, а всё остальное — на свой страх и риск (разрабы модуля могут трогать остальной код модуля когда вздумается и сам себе злобный буратин, если завязался на что-то вне ./Api/).

Раз привязки к файловой системе нет, где его искать непонятно.

Каждый composer compatible модуль привязывает (autoload) свой namespace к собственной файловой структуре в файле composer.json (аналог package.json в npm). В результате composer (менеджер зависимостей в PHP) при сборке проекта из отдельных модулей создаёт карту соответствия пространств имён фрагментам файловой системы. После чего вам достаточно подключить в вашем головном файле автозагрузчик, который и использует эту карту:


<?php
require_once __DIR__ . '/../vendor/autoload.php';

и вы можете создавать новые классы, основываясь на их именах:


$dbalCfg = new \Doctrine\DBAL\Configuration();

или


use Doctrine\DBAL\Configuration;

$dbalCfg = new Configuration();

или


use Doctrine\DBAL\Configuration as Config;

$dbalCfg = new Config();

Карта обновляется по мере подключения/отключения модулей


composer require vendor/module

и может быть использована также и IDE для поиска исходников по имени класса.

В спецификации веб-стандартов есть предложение использовать карту импортов. Эта карта будет описывать маппинги импортируемых имен на полные пути на файловой системе (или URL, если в браузере). Сможет ли эта штука дать вам поведение и гибкость, которую вы хотите?

Нет, не похоже. Проблемы с адресацией элементов кода в проекте это не решает.

Вы о копированнии ссылки на исходник в PhpStorm? На экспорт ES-модуля в теории можно сослаться. Вот код


// ./src/foo/bar/baz.js
export class MyClass {}

Идентификатор вида ./src/foo/bar/baz.js#MyClass будет однозначно определять ваш класс. WebStorm/PhpStorm генерировать такие ссылки не может, но только потому что на это нет спроса в сообществе. Все и так справляются.


Главное, что эти экспорты поддерживаются автоматическим рефакторингом, то есть при переименовании MyClass, все места его использования обновятся автоматически. Для поддержки больших проектов это важнее копирования ссылок в чатик.

Я специально нигде ни в статье, ни в комментах не использовал use, вы сами первый его написали (можете проверить по Ctrl+F) :) Но если после этого, кому-то что-то стало понятнее, то и хорошо.

Вы и про import ничего не писали =) Просто для JS разработчиков не совсем понятно чем отличается


use Some\Any as Alias;
// от
import {Any as Alias} from 'some/any';

и именование "Some\Any" от имени "SomeAny".


Если рассматривать с этой стороны, то думаю становится понятным почему JS разработчики не понимают смысла существования оных.

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

import { Configuration as dbalConfig } from 'doctrine/DBAL'
Вот этот вот код — это императивная конструкция.

Согласен, но при написании кода — это не представляет никаких трудностей. Как я выше написал — стиль кода будет другой. На ноде нет огромных фрэймворков с кучей классов, которые одинаково называются.
Тут скорее проблематика другая. В JS есть манкипатчинг, по-этому конфликт имён не предоставляет больших проблем.

Предлагаю представить какой хаос творился бы, если бы название функций и классов обязано бы было быть уникальным вообще во всём проекте.
UFO just landed and posted this here

А покажите пример приложения в котором это работает? По моим сведениям, ванильный node без дополнительных модулей так не сможет

В ts есть стандартный путь — алиясы путей, поддерживаемый автокомплитом по крайней мере в vscode.
Пример tsconfig
{
    "compilerOptions": {
        "allowJs": false,
        "alwaysStrict": true,
        "baseUrl": ".",
        "downlevelIteration": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "importHelpers": true,
        "module": "esnext",
        "moduleResolution": "node",
        "noEmitHelpers": true,
        "noEmitOnError": true,
        "removeComments": false,
        "resolveJsonModule": true,
        "target": "esnext",
        "lib": [
            "dom",
            "esnext"
        ],
        "paths": {
            "@crud": [
                "./src/core/crud/index.ts"
            ],
            "@crud/*": [
                "./src/core/crud/*"
            ],
            "@entities": [
                "./src/core/entities/index.ts"
            ],
            "@entities/*": [
                "./src/entities/*",
                "./src/core/entities/*"
            ],
            "@frontend/*": [
                "./src/frontend/*",
                "./src/core/frontend/*"
            ],
            "@ioc": [
                "./src/core/ioc/index.ts"
            ],
            "@ioc/*": [
                "./src/core/ioc/*"
            ],
            "@middlewares/*": [
                "./src/middlewares/*",
                "./src/core/middlewares/*"
            ],
            "@subscribers": [
                "./src/core/subscribers/index.ts"
            ],
            "@subscribers/*": [
                "./src/subscribers/*",
                "./src/core/subscribers/*"
            ],
            "@templates/*": [
                "./src/templates/*",
                "./src/core/templates/*"
            ],
            "@utils/*": [
                "./src/utils/*",
                "./src/core/utils/*"
            ]
        }
    },
    "exclude": [
        "static",
        "build",
        "dist",
        "node_modules"
    ]
}


Для интеграции с webpack есть github.com/dividab/tsconfig-paths-webpack-plugin, который позволяет использовать эти алиясы для всех лоадеров, использующих апи вебпака для ресолва путей.
Вот-вот. Есть решение для typescript, есть костыль для webpack. А стандартного решения из коробки, чтобы работало на ванильной node безо всяких модулей – нет.
а для интеграции с нодой что делать?
justboris, worldxaker
Для ванильной ноды так или иначе приходится пользоваться вебпаком, ибо полноценные модули в ноду без хармони флагов пока не завезли. + у вебпака и прочих плюшек хватает.
а, ок. я просто один ts юзаю. мне хватает всё кроме алиасов

Получается, что мы уже пишем не на ванильном JS, а на каком-то WebpackScript, что не очень хорошо, потому что переносимость кода страдает.

Как раз таки с «WebpackScript» переносимость повышается, ибо есть куча плюшек способных автоматом добавлять полифиллы рантайма, систем модулей, и самого языка.
Переносимость в том смысле, что можно обработать исходник вебпаком, чтобы он запускался и в node, и в браузере – это да, есть такое

Но я имел в виду переносимость исходников. Заходите вы переехать с вебпака на что-то более новое и удобное – но не сможете, потому что у вас в коде есть завязки на конкретные фичи сборщика.
Вы придираетесь.
Если бы речь шла скажем о с++, где есть 2 основных сильно разнящихся компилятора (msvc и gcc, остальные более-менее совместимы с gcc), то стоит добиваться совместимости со всеми «сборщиками».

Но касательно node и браузеров в 2019 по сути остался единственный сборщик для новых проектов — webpack. Можно поспорить конечно насчет rollup, но имхо в повседневной разработке, где нет цели экономить на каждом байтике в результирующем бандле, вебпаку нет альтернативы, благодаря выстроенному комьюнити и числу готовых решений.

Gulp, Grunt — мертвы, посмотрите даты релизов последних версий.
А больше ничего достаточно популярного и не было.

Да и вообще вашу логику можно перефразировать как:
Вот пишите вы на языке А, а вдруг потом нужно будет переехать на более новый и удобный язык B — не сможете! Ой как не хорошо.
Для оценки этой роли достаточно попробовать разбить какой-нибудь npm-модуль на несколько модулей поменьше и перестроить свой проект на использование двух новых модулей вместо одного большого.

Такое в Python тоже можно провернуть, используя только `import`, но импортируя сторонние имена через свой промежуточный модуль.

А ещё в Python можно переименовать модуль:
import module as alias

Другое дело, что такие трюки приводят к нечитаемости кода − другой программист не сразу поймёт, с какими именно библиотеками вы работаете. Мне кажется, к PHP это тоже относится.
Мне последнее время кажется более эффективным подход, когда код пишется без пространства имен, а пространства имен для внешних модулей устанавливаются при подключении стороннего кода. Единственное возникает проблема смены пространств имен в стороннем коде, но при современном уровне парсеров кода это не так сложно.

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

Sign up to leave a comment.

Articles