Я программирую на PHP. И немножко на JS. Когда-то я программировал на Java, ещё раньше — на LotusScript. Попробовал на вкус python и dart. Basic, Fortran, Pascal, Prolog, VisualBasic, С++/С, perl — на всём этом я тоже изображал что-то исполняемое. Языки программирования меня интересуют с точки зрения создания компьютерных приложений. Web-приложений. Сложных web-приложений. Таких, которые пишут незнакомые друг с другом люди. Точнее, лично незнакомые — они знают друг друга по подписям в коммитах в общий репозиторий и по nickname’ам в баг-трекерах. Я не слишком умён, чтобы программировать на С/С++ для различных ОС, и поэтому я программирую на PHP для Magento.


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


В данном тексте под пространством имён я подразумеваю namespace с точки зрения PHP, а не namespace с точки зрения python’а:


<?php
namespace Vendor\Project\Module\Component\Unit;

Впервые с пространством имён я столкнулся при изучении Java, когда пытался постичь тайну директивы "package":


package com.sun.source.util;

Было непонятно назначение этой директивы и что именно в ней указывать, если указывать можно было любую строку. Рекомендация от авторов языка использовать в качестве части названия пакета зарегистрированного на тебя (на твою компанию) домена выглядело несколько экстравагантно. Это сейчас каждый-всякий-любой имеет свой собственный домен и такая рекомендация не сильно смущает, а 15-20 лет назад я очень сильно думал, какой домен взять в качестве названия для своего первого пакета и на что это может повлиять в дальнейшем. Только впоследствии, когда я собирал приложения с помощью maven’а, я оценил прозорливость данной рекомендации.


Менеджеры зависимостей


Понять значение пространства имён мне помогли менеджеры зависимостей. Если твой код использует сторонний, который зависит от других пакетов, зависящих от третьих — в такой свалке очень трудно поддерживать порядок. Тем не менее, именно из-за обратно-доменного правила наименования пакетов в куче JAR’ов, сваленных в один каталог (например, в WEB-INF/lib), достаточно легко ориентироваться:


image


Сравните с npm (JavaScript):


image


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


В PHP менеджер зависимостей composer создаёт двухуровневую структуру каталога: ./company/module:


image


что даёт некоторое преимущество в навигации по зависимостям перед одноуровневым размещением.


Вот статистика по центральным репозиториям пакетов для Java/JS/PHP:


https://mvnrepository.com/repos/central — 3 358 578 indexed jars
https://www.npmjs.com/ — 872 459 packages
https://packagist.org/statistics — 207?560 packages (1?472?944 versions)


Скорее всего для maven’а в статистике учитываются все версии модулей, в то время, как в npm и composer учитываются именно сами модули.


Для чего нужно пространство имён?


Основной ответ — для предотвращения конфликтов различных элементов кода (константы, функции, классы, ...), имеющих одинаковые имена, но находящихся в различных модулях. С этим успешно справляются "пространства имён" по версии python’а. Но я бы всё-таки взял здесь "пространство имён" в кавычки, т.к. по сути своей это ближе к области видимости (scope).


Пространство имён по версии Java (package) и PHP (namespace) прежде всего позволяет однозначно адресовать конкретный элемент кода в совокупной общности. И вот это вот свойство пространства имён (логическая группировка) и даёт возможность создавать более сложные программные комплексы менее связанными друг с другом группами разработчиков.


Адресация программных элементов


В PHP класс \Doctrine\DBAL\Schema\Column адресуется однозначно, каким бы образом не подключался исходный код к проекту. IDE способно без труда сформировать этот адрес. В PhpStorm это делается так (правой кнопкой по элементу кода):


image


Тот же PhpStorm теряется, если применить подобный приём для JS-кода (где нет namespace’ов). Попробуем подобным образом сформировать адрес для ссылки на JS-функцию query:


image


На выходе имеем module.query, что недостаточно информативно.


Для адресации функции query в документации (переписке, баг-трекере и т.п.) приходится ссылаться на конкретную строку кода в файле:


image


Результат: ./node_modules/express/lib/middleware/query.js:25


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


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


Обнаружение конфликтующих версий кода


Современные сложные приложения не могут разрабатываться без менеджеров зависимостей (maven, composer, npm, ...). При этом наши зависимости тянут свои зависимости, которые тянут свои и т.д., что в результате может приводить к конфликтам по версиям для одного и того же пакета, подтянутого через различные зависимости (jar hell).


В JS подобного не возникает в силу отсутствия namespace’ов. Я сам сталкивался с ситуацией, когда при установке в Magento дополнительных модулей количество подгружаемых ими различных версий библиотеки jQuery переваливало за 5-6. С одной стороны, подобное поведение даёт бОльшую свободу самим разработчикам, с другой — бОльшая свобода предъявляет и бОльшие требования к квалификации. Ну а поиск ошибок в такой разноверсионной лапше зависимостей — квалификации на порядок-два выше, чем квалификации для создания этих самых ошибок.


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


image


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


Автозагрузка кода


Функция spl_autoload_register в PHP позволяет разработчику не заморачиваться тем, где именно находятся файлы с исходниками его классов. В любом проекте можно переопределить эту функцию и реализовать собственный алгоритм загрузки скриптов по имени класса. Без применения пространства имён приходилось выписывать довольно кучерявые имена для классов, чтобы обеспечить их уникальность в пределах сложного проекта (особенно с учётом сторонних библиотек). В Zend1 абстрактный адаптер для работы с БД определялся таким образом:


abstract class Zend_Db_Adapter_Abstract {}

Для обеспечения уникальности приходилось, по сути, добавлять namespace в имя класса. Само собой, при использовании таких имён классов в коде приходится шире водить глазами по строкам.


В Zend2, где уже используются namespaces, аналогичное определение класса выглядит так:


namespace Zend\Db\Adapter;
class Adapter implements ... {}

Код в итоге становится более читаемым, но самым значимым результатом применения пространства имён становится возможность унификации функционала загрузчика классов с привязкой логической иерархии классов к файловой структуре. Вот выдержка из файла ./vendor/composer/autoload_namespaces.php, который создаёт composer в PHP для работы загрузчика ./vendor/autoload.php:


<?php
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Zend_' => array($vendorDir . '/magento/zendframework1/library'),
    'Yandex' => array($vendorDir . '/allure-framework/allure-codeception/src', $vendorDir . '/allure-framework/allure-php-api/src', $vendorDir . '/allure-framework/allure-php-api/test'),
    'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src'),
    'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src'),
    'PhpCollection' => array($vendorDir . '/phpcollection/phpcollection/src'),
    'PHPMD\\' => array($vendorDir . '/phpmd/phpmd/src/main/php'),
    'OAuth\\Unit' => array($vendorDir . '/lusitanian/oauth/tests'),
    'OAuth' => array($vendorDir . '/lusitanian/oauth/src'),
    ...

Видно, что исходники в разных библиотеках могут располагаться по различным путям (различные внтуримодульные структуры), а composer при формировании проекта создаёт карту наложения логической иерархии классов на файловую систему. И пространства имён играют в этом наложении значимую роль.


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


IoC


Идентификатором объектов в IoC-контейнерах является строка (по крайней мере, в PHP). В простых примерах вполне допустимо использовать идентификаторы типа dbAdapter, serviceA, serviceB и т.д. Но чем крупнее проект, тем сложнее ориентироваться, в каком месте происходит создание объекта с идентификатором, например, searchFilterList и где он используется. Логичным выходом является использование в качестве идентификаторов объектов имён классов. В таком случае логика создания объектов контейнером становится предсказуемой, а исходный код и места использования элементарно определяются IDE. Пространство имён позволяет организовать все классы проекта в одной логической структуре и использовать соответствующие пути при создании объектов контейнером.


Резюме


В свете вышесказанного я считаю, что языки программирования, нативно использующие пространства имён для структурирования исходного кода при помощи логической группировки его элементов позволяют с меньшими затратами строить более сложные приложения, чем языки, подобной логической группировки не имеющие. Соответственно, максимальная сложность приложений, которые можно создать на Java/PHP/C++/..., не может быть достигнута разработчиками с аналогичной квалификацией на JavaScript/Python/C/....

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


  1. justboris
    02.01.2019 23:19

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


    В результате, в 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-модулях локальные, придумывать уникальные имена в рамках всего проекта не надо.


    1. flancer Автор
      03.01.2019 10:34

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


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

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


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


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


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

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


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

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


      1. justboris
        03.01.2019 11:15
        -1

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

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


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


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


        1. rjhdby
          04.01.2019 18:34
          +1

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

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


          1. justboris
            04.01.2019 21:36
            +1

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


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


            1. flancer Автор
              04.01.2019 00:07

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


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


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



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


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


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


              1. justboris
                04.01.2019 01:38

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


                1. flancer Автор
                  04.01.2019 09:23

                  В вашем проекте папки и файлы организованы так, в моём — по-другому, у кого-то — по третьему. Посмотрите содержимое файла 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 файлов разработчиков со своими модулями, то пространство имён становится более актуальным.


                  1. justboris
                    04.01.2019 11:54
                    -1

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

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


                    1. flancer Автор
                      04.01.2019 12:41

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


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


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


                      image


                      А в CodeIgniter так:


                      image


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


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


                      1. SerafimArts
                        04.01.2019 20:40
                        +1

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


                        1. flancer Автор
                          04.01.2019 20:43

                          Да, троечка с namespace'ами. Вы реабилитировали Yii в моих глазах, спасибо :)


                      1. justboris
                        04.01.2019 22:57

                        Да, для 1000 файлов они выкрутятся (какой ценой?), а для 300 млн.?

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


                1. rjhdby
                  04.01.2019 10:38
                  +1

                  Затем, что это совершенно разные вещи.
                  В автобусном парке есть два икаруса и один лиаз с номерами Н123С, Р778Т и М543Н.
                  С другой стороны, автобусный парк обслуживает маршруты 711, 215 и 987.
                  Даже не смотря на то, что мы можем уверенно сказать, что икарус Н123С обслуживает маршрут 711 — это все равно две разные, не связные между собой, плоскости абстракций.


                  1. justboris
                    04.01.2019 12:02

                    Если следовать вашей аналогии, то можно сказать, что неймспейс и путь до файла с классом вообще никак не похожи. Но разве класс `\Prophecy\Call\CallCenter()` может внезапно смаппиться на файловую систему как `bla/bla/bla`?


                    1. flancer Автор
                      04.01.2019 12:44

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


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


                      1. SerafimArts
                        04.01.2019 20:42
                        +1

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


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


                        1. flancer Автор
                          04.01.2019 20:53

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


                          1. SerafimArts
                            04.01.2019 21:00

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


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

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


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

                            Ну разве что только для отладки кода какого-нибудь можно трогать.


  1. vasyan
    02.01.2019 23:25
    -1

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


    Абсолютно недоказанное утверждение. Никому отсутствие namespace не мешает говнокодить огромные приложения на javascript. В JS область видимости очень ограничена модулями. Два модуля с одним именем, ну не знаю, кому это может быть нужно.
    Сколько пишу на JS никогда не сталкивался с тем, что мне нужны были namespacе.


  1. justboris
    02.01.2019 23:30
    +2

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


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

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


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


    1. Gugic
      03.01.2019 04:20
      +1

      Приходите в тайпскрипт:

      // tsconfig.json
      {
        "compilerOptions": {
          // ...
          "baseUrl":"./path/to/src",
          // ...
        },
        // ...
      }
      

      // actual file with import
      import {UserService} from 'services/user/UserService';
      // ...
      


      1. justboris
        03.01.2019 10:56

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


        1. YemSalat
          04.01.2019 14:08

          Typescript же не перезапишет пути

          При транспиляции он «развернет» их в относительные. Или я вас нe понял?


          1. Gugic
            04.01.2019 22:39
            +1

            Я проверил и — таки нет, justboris тут прав. Я довольно давно настраивал билд у себя и у меня этот резолюшен в транспилированном коде делался через вебпак.

            Вот issue на этот счет для typescript, и это не собираются чинить: github.com/Microsoft/TypeScript/issues/10866.


    1. Fedcomp
      03.01.2019 06:19

      NODE_PATH вроде решает эту проблему. Думаю вебпак тоже мжно настроить.


      1. justboris
        03.01.2019 11:01

        Решает, но нет единообразного решения. Сборщик, тест-раннер, typescript — всем нужно указывать путь своим особым способом.

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


    1. flancer Автор
      03.01.2019 09:58

      Файлы из своего проекта можно импортировать только через относительные пути.

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


      1. YemSalat
        04.01.2019 15:16

        Ну так в JS в этом планe просто «другая парадигма»

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

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


        1. flancer Автор
          04.01.2019 16:01

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


          1. YemSalat
            04.01.2019 16:12

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

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


            1. flancer Автор
              04.01.2019 16:39

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



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


              1. YemSalat
                04.01.2019 18:12

                Но в JSe вам достаточно просто сделать

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

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


                1. flancer Автор
                  04.01.2019 18:24

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


                  1. YemSalat
                    04.01.2019 18:38

                    По сути — это модуль.

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

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

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


                    1. flancer Автор
                      04.01.2019 18:58

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


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


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

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


                      1. YemSalat
                        05.01.2019 01:58

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

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

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

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


                        1. flancer Автор
                          05.01.2019 02:35

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


                1. SerafimArts
                  04.01.2019 20:57
                  +1

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


                  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", который генерируется каким-нибудь оптимизатором или препроцессором.


                  1. justboris
                    04.01.2019 23:06

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


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


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


                    1. SerafimArts
                      04.01.2019 23:49

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

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

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

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

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


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


                    1. flancer Автор
                      04.01.2019 23:52

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

                      Каждый 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 для поиска исходников по имени класса.


                  1. flancer Автор
                    04.01.2019 23:18

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


                    1. SerafimArts
                      04.01.2019 23:58
                      +1

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


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

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


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


    1. NelGarbuzova
      04.01.2019 15:09

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

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


      К тому же в любой папке можно создавать отдельные локальные модули (с package.json), используя их также как ключевые точки в путях. К примеру, в папку services запихиваете package.json с «name»: «services» и тогда вместо вашего нагромождения можно просто написать:
      import UserService from 'services/user/UserService.js';


      Этот подход можно применять и в более мелких модулях, как заблагорассудится. Тогда вы можете вообще импортировать просто:
      import UserService from 'UserService';


      1. justboris
        04.01.2019 21:30

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


    1. Taraflex
      04.01.2019 03:28

      В 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"
          ]
      }
      


      1. justboris
        04.01.2019 12:04

        Вот-вот. Есть решение для typescript, есть костыль для webpack. А стандартного решения из коробки, чтобы работало на ванильной node безо всяких модулей – нет.


      1. worldxaker
        04.01.2019 15:48

        а для интеграции с нодой что делать?


        1. Taraflex
          04.01.2019 17:13

          justboris, worldxaker
          Для ванильной ноды так или иначе приходится пользоваться вебпаком, ибо полноценные модули в ноду без хармони флагов пока не завезли. + у вебпака и прочих плюшек хватает.


          1. worldxaker
            04.01.2019 22:38

            а, ок. я просто один ts юзаю. мне хватает всё кроме алиасов


          1. justboris
            04.01.2019 23:10

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


            1. Taraflex
              04.01.2019 23:24

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


              1. justboris
                05.01.2019 00:16

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

                Но я имел в виду переносимость исходников. Заходите вы переехать с вебпака на что-то более новое и удобное – но не сможете, потому что у вас в коде есть завязки на конкретные фичи сборщика.


                1. Taraflex
                  05.01.2019 00:31

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

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

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

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


  1. Tanner
    03.01.2019 05:30

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

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

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

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


  1. search
    03.01.2019 08:34

    В npm тоже есть scopes, если что.


    https://docs.npmjs.com/about-scopes


  1. MetaAbstract
    03.01.2019 13:52
    +1

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


    1. flancer Автор
      04.01.2019 14:33

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