Я программирую на 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
), достаточно легко ориентироваться:
Сравните с npm
(JavaScript
):
В Java
разработчиками достаточно широко принято "обратно-доменное" наименование пакетов (как следствие — модулей), а в JS
— нет. В результате, в Java
можно независимо создать большое количество бесконфликтных пакетов (модулей) без явного согласования их наименования независимыми группами разработчиков, а в JS
для этого нужно явно использовать реестр npm. Да, в Java
в разрешении конфликтов неявным образом задействован глобальный реестр доменов, но это же правило наименования может использовать любое сообщество, а не только Java
-кодеры.
В PHP
менеджер зависимостей composer
создаёт двухуровневую структуру каталога: ./company/module
:
что даёт некоторое преимущество в навигации по зависимостям перед одноуровневым размещением.
Вот статистика по центральным репозиториям пакетов для 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 это делается так (правой кнопкой по элементу кода):
Тот же PhpStorm теряется, если применить подобный приём для JS
-кода (где нет namespace’ов). Попробуем подобным образом сформировать адрес для ссылки на JS
-функцию query
:
На выходе имеем module.query
, что недостаточно информативно.
Для адресации функции query
в документации (переписке, баг-трекере и т.п.) приходится ссылаться на конкретную строку кода в файле:
Результат: ./node_modules/express/lib/middleware/query.js:25
Разумеется, при изменении кол-ва строк в файле или перемещении/переименовании файла мы будем иметь в документации устаревший адрес интересующего нас программного элемента.
Таким образом, использование пространства имён позволяет ссылкам на различные элементы кода проекта оставаться актуальными гораздо дольше, чем ссылки на строку в файле.
Обнаружение конфликтующих версий кода
Современные сложные приложения не могут разрабатываться без менеджеров зависимостей (maven
, composer
, npm
, ...). При этом наши зависимости тянут свои зависимости, которые тянут свои и т.д., что в результате может приводить к конфликтам по версиям для одного и того же пакета, подтянутого через различные зависимости (jar hell).
В JS
подобного не возникает в силу отсутствия namespace’ов. Я сам сталкивался с ситуацией, когда при установке в Magento
дополнительных модулей количество подгружаемых ими различных версий библиотеки jQuery
переваливало за 5-6. С одной стороны, подобное поведение даёт бОльшую свободу самим разработчикам, с другой — бОльшая свобода предъявляет и бОльшие требования к квалификации. Ну а поиск ошибок в такой разноверсионной лапше зависимостей — квалификации на порядок-два выше, чем квалификации для создания этих самых ошибок.
Использование namespace’ов в PHP
позволяет легко обнаруживать подобные конфликты на уровне IDE (для примера я сделал второй файл с дубликатом класса внутри):
Таким образом, задача по обнаружению дубликатов элементов кода в проекте становится достаточно легко выполнимой.
Автозагрузка кода
Функция 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)
vasyan
02.01.2019 23:25-1максимальная сложность приложений, которые можно создать на Java/PHP/C++/..., не может быть достигнута разработчиками с аналогичной квалификацией на JavaScript/Python/C/....
Абсолютно недоказанное утверждение. Никому отсутствие namespace не мешает говнокодить огромные приложения на javascript. В JS область видимости очень ограничена модулями. Два модуля с одним именем, ну не знаю, кому это может быть нужно.
Сколько пишу на JS никогда не сталкивался с тем, что мне нужны были namespacе.
justboris
02.01.2019 23:30+2А еще не рассказали про самое главное неудобство при работе с Javascript-модулями. Файлы из своего проекта можно импортировать только через относительные пути. Из-за этого получаются ужасные цепочки. Реальный код:
import UserService from '../../../../src/services/user/UserService.js';
Стандартной возможности написать импорт относительно корня проекта нет. Есть разного рода костыли (34 штуки!), но поскольку они нестандартные, то с ними регулярно что-то отваливается.
Введение нормального способа импортировать относительно корня проекта подарит нам возможность грамотнее структурировать модули, это точно.
Gugic
03.01.2019 04:20+1Приходите в тайпскрипт:
// tsconfig.json { "compilerOptions": { // ... "baseUrl":"./path/to/src", // ... }, // ... }
// actual file with import import {UserService} from 'services/user/UserService'; // ...
justboris
03.01.2019 10:56Не поможет. Typescript же не перезапишет пути, в скомпилированном коде они так и останутся, и все равно придется использовать один из тех воркэраундов.
YemSalat
04.01.2019 14:08Typescript же не перезапишет пути
При транспиляции он «развернет» их в относительные. Или я вас нe понял?Gugic
04.01.2019 22:39+1Я проверил и — таки нет, justboris тут прав. Я довольно давно настраивал билд у себя и у меня этот резолюшен в транспилированном коде делался через вебпак.
Вот issue на этот счет для typescript, и это не собираются чинить: github.com/Microsoft/TypeScript/issues/10866.
Fedcomp
03.01.2019 06:19NODE_PATH вроде решает эту проблему. Думаю вебпак тоже мжно настроить.
justboris
03.01.2019 11:01Решает, но нет единообразного решения. Сборщик, тест-раннер, typescript — всем нужно указывать путь своим особым способом.
В нормальной системе это должно указываться в одном месте, чтобы все инструменты подхватывали автоматически. NODE_PATH мог бы быть таким стандартом, но его намеренно выпиливают из реализации ES-модулей в Node: nodejs.org/api/esm.html#esm_no_node_path
flancer Автор
03.01.2019 09:58Файлы из своего проекта можно импортировать только через относительные пути.
Этот коммент замечательно иллюстрирует сказанное в пункте "Автозагрузка кода". Нам приходится отталкиваться от файловой структуры, если нам сложно отталкиваться от структуры кода.
YemSalat
04.01.2019 15:16Ну так в JS в этом планe просто «другая парадигма»
Использование namespace’ов в PHP позволяет легко обнаруживать подобные конфликты на уровне IDE (для примера я сделал второй файл с дубликатом класса внутри):
Ваш пример не имеет особого смысла в Node, т.к. там декларации классов не попадают в глобальный скоуп. Т.е. можно в двух соседних файлах объявить класс с одинаковым названием и это не будет ошибкой.flancer Автор
04.01.2019 16:01Вы правильно сказали — "не попадают в глобальный скоуп". В JS вообще нет namespace'ов в смысле логической группировки элементов кода. Есть scope. Именно поэтому на JS'е сложно писать сложные проекты — сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены. А в PHP можно. И в Java можно. И в Typescript можно (если я правильно понял).
YemSalat
04.01.2019 16:12сложно уникально адресовать два класса с одинаковым названием
Вы можете привести пример из жизни (не сферический) где такое встречается и именно namespace'ы выручают?flancer Автор
04.01.2019 16:39Я не программирую на TypeScript, а в JS нет namespace'ов (поэтому они не выручат). Что касается PHP, то вот два класса с одинаковым именем
Configuration
, конфликт которых "разводят" namespace'ы (оба класса, что характерно, находятся в файлахConfiguration.php
):
Для того, чтобы создать объекты этих классов в современном PHP-приложении мне достаточно знать их полные имена, все остальное делает менеджер зависимостей (autoloading).
YemSalat
04.01.2019 18:12Но в JSe вам достаточно просто сделать
import { Configuration as dbalConfig } from 'doctrine/DBAL' import { Configuration as ormConfig } from 'doctrine/ORM'
То есть там в принципе не стоит такой проблемы.flancer Автор
04.01.2019 18:24Что такое
doctrine/DBAL
иdoctrine/ORM
? Каталог, файл или некоторая строковая метка для поиска пути к исходникам по некоторому алгоритму?YemSalat
04.01.2019 18:38По сути — это модуль.
Каталог, файл или некоторая строковая метка для поиска пути к исходникам по некоторому алгоритму?
А какая разница? Если при наборе from 'doctrine/...` вам ide так же выдаст список доступных импортов.
Стиль кода будет немного иной, понятно что в JS так исторически сложилось что модульность там прикручена сверху уже сложившейся платформы, но при написании кода это почти никогда мешает. Да, на локальные модули приходится relative path писать, но как вы написали есть 100500 способов это разрулить.
Я сейчас как раз в основном пишу на JS и Python, и я там ни разу не сталкивался с проблемами в стиле «мне нужно импортировать два класса с одинаковым названием и это не решается алиасами»flancer Автор
04.01.2019 18:58Вы по ходу дела плавно подменили первоначальную проблему "сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены" другой — "мне нужно импортировать два класса с одинаковым названием и это не решается алиасами" :)
Дело в том, что в PHP-проекте я могу написать:
$init = new \Doctrine\DBAL\Event\Listeners\MysqlSessionInit()
без того, чтобы использовать
import
илиrequire
. При этом я не должен знать о том, в каком каталоге находится файл, в котором находится исходный код. Именно потому, что в PHP/Java/TypeScript/… есть директиваnamespace
. А в JS/Python этой директивы нет, вы её никогда не использовали и не понимаете для чего она вообще может быть нужна. Если вам она не нужна, то это говорит только о том, что вы ещё не достигли своего предела сложности в JS/Python.YemSalat
05.01.2019 01:58Вы по ходу дела плавно подменили первоначальную проблему «сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены» другой — «мне нужно импортировать два класса с одинаковым названием и это не решается алиасами»
А в чем разница? В моем примере выше никакой привязки к файлам нету.
При этом я не должен знать о том, в каком каталоге находится файл, в котором находится исходный код. Именно потому, что в PHP/Java/TypeScript/… есть директива namespace. А в JS/Python этой директивы нет, вы её никогда не использовали и не понимаете для чего она вообще может быть нужна. Если вам она не нужна, то это говорит только о том, что вы ещё не достигли своего предела сложности в JS/Python.
Так себе критерий оценки сложности проектов над которыми я работаю. Сложностью можно управлять по-разному, нe обязательно городить здоровенные монолиты с миллиардами классов, где без нэймспэйсов можно потеряться.
Если вам так нравится думать о там как вам namespace'ы позволяют писать крутой код — пожалуйста, но ваши аргументы для вывода статьи не убедительны.flancer Автор
05.01.2019 02:35А я статью писал не для того, чтобы кого-то в чём-то убедить. Я хотел увидеть, что серьёзных возражений против этого моего вывода не будет. Их и нет.
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", который генерируется каким-нибудь оптимизатором или препроцессором.justboris
04.01.2019 23:06Вот, теперь стало понятнее. Только остается вопрос: если есть столько разнообразных способов загрузить себе класс, то как это дебажить в случае неполадок?
Допустим, вы сделали небольшую правку, а приложение упало с сообщением, что не может найти какой-то класс, с вашими изменениями совсем не связанный. Раз привязки к файловой системе нет, где его искать непонятно.
Спрашиваю безо всякого троллинга, мне и правда интересно как такое решается.
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
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 для поиска исходников по имени класса.
flancer Автор
04.01.2019 23:18Я специально нигде ни в статье, ни в комментах не использовал
use
, вы сами первый его написали (можете проверить по Ctrl+F) :) Но если после этого, кому-то что-то стало понятнее, то и хорошо.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 разработчики не понимают смысла существования оных.
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';
justboris
04.01.2019 21:30А покажите пример приложения в котором это работает? По моим сведениям, ванильный node без дополнительных модулей так не сможет
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" ] }
justboris
04.01.2019 12:04Вот-вот. Есть решение для typescript, есть костыль для webpack. А стандартного решения из коробки, чтобы работало на ванильной node безо всяких модулей – нет.
worldxaker
04.01.2019 15:48а для интеграции с нодой что делать?
Taraflex
04.01.2019 17:13justboris, worldxaker
Для ванильной ноды так или иначе приходится пользоваться вебпаком, ибо полноценные модули в ноду без хармони флагов пока не завезли. + у вебпака и прочих плюшек хватает.justboris
04.01.2019 23:10Получается, что мы уже пишем не на ванильном JS, а на каком-то WebpackScript, что не очень хорошо, потому что переносимость кода страдает.
Taraflex
04.01.2019 23:24Как раз таки с «WebpackScript» переносимость повышается, ибо есть куча плюшек способных автоматом добавлять полифиллы рантайма, систем модулей, и самого языка.
justboris
05.01.2019 00:16Переносимость в том смысле, что можно обработать исходник вебпаком, чтобы он запускался и в node, и в браузере – это да, есть такое
Но я имел в виду переносимость исходников. Заходите вы переехать с вебпака на что-то более новое и удобное – но не сможете, потому что у вас в коде есть завязки на конкретные фичи сборщика.Taraflex
05.01.2019 00:31Вы придираетесь.
Если бы речь шла скажем о с++, где есть 2 основных сильно разнящихся компилятора (msvc и gcc, остальные более-менее совместимы с gcc), то стоит добиваться совместимости со всеми «сборщиками».
Но касательно node и браузеров в 2019 по сути остался единственный сборщик для новых проектов — webpack. Можно поспорить конечно насчет rollup, но имхо в повседневной разработке, где нет цели экономить на каждом байтике в результирующем бандле, вебпаку нет альтернативы, благодаря выстроенному комьюнити и числу готовых решений.
Gulp, Grunt — мертвы, посмотрите даты релизов последних версий.
А больше ничего достаточно популярного и не было.
Да и вообще вашу логику можно перефразировать как:
Вот пишите вы на языке А, а вдруг потом нужно будет переехать на более новый и удобный язык B — не сможете! Ой как не хорошо.
Tanner
03.01.2019 05:30Для оценки этой роли достаточно попробовать разбить какой-нибудь npm-модуль на несколько модулей поменьше и перестроить свой проект на использование двух новых модулей вместо одного большого.
Такое в Python тоже можно провернуть, используя только `import`, но импортируя сторонние имена через свой промежуточный модуль.
А ещё в Python можно переименовать модуль:
import module as alias
Другое дело, что такие трюки приводят к нечитаемости кода ? другой программист не сразу поймёт, с какими именно библиотеками вы работаете. Мне кажется, к PHP это тоже относится.
MetaAbstract
03.01.2019 13:52+1Мне последнее время кажется более эффективным подход, когда код пишется без пространства имен, а пространства имен для внешних модулей устанавливаются при подключении стороннего кода. Единственное возникает проблема смены пространств имен в стороннем коде, но при современном уровне парсеров кода это не так сложно.
flancer Автор
04.01.2019 14:33Если это позволяет вам выстраивать единую структуру кода для вашего проекта и ориентироваться в ней на уровне элементов кода (классы, функции, константы, ...), а не на уровне файлов и строк, то почему бы и нет.
justboris
В целом, соглашусь с тезисом статьи, пространства имен – это хорошо и полезно. Но хочу немного уточнить некоторые фактические моменты:
Npm использовать необязательно. Вы можете опубликовать пакет на Github/Gitlab и добавить себе его в зависимости
Полный список поддерживаемых форматов есть в документации
Еще можно поднять свой собственный репозиторий и публиковать пакеты в него. Если в компании куплена Artifactory Pro, то там это идет из коробки
То есть, если вы не публикуете пакет в публичный npm, а используете его приватно, можете называть его как угодно.
Так это недостающая фича в PhpStorm, а не принципиальный косяк npm. Ничто не мешает генерировать ссылку типа такой:
npm-module-name/path/to/file.js:123
.В личной практике мне никогда это особо не мешало, ссылки на исходники коллегам скидываю на Github/Gitlab/Bitbuket и т.д.
Вот этот вывод непонятно как получился. Имена классов в JS-модулях локальные, придумывать уникальные имена в рамках всего проекта не надо.
flancer Автор
Спасибо за развёрнутый коммент, коллега.
Вот за то и речь. Называя как угодно свои приватные модули, мы уменьшаем себе возможность их использования в глобальных проектах. Допустим, вы сделали приватный модуль
debug
для своего проекта, выложили на корпоративный репозиторий и используете его в своих проектах. В какой-то момент ваша корпорация поглощает другую корпорацию и вашему IT-отделу ставится задача адаптировать их замечательные наработки в ваши. А у них такой же модульdebug
используется. Вот и получаем вилы на ровном месте.В java-мире, например, корпорация Oracle поглотила корпорацию Sun, а пакет
com.oracle.json
замечательно сосуществуют рядом с пакетомcom.sun.net.httpserver
.А представьте, что вы бы захотели опубликовать свой приватный пакет
debug
для открытого использования?Это всё равно привязка к тексту в файле, а не к логической структуре кода (константа, метод, класс, ...). Адресация поплывет при изменении кол-ва строк в файле.
Вполне возможно, что мой прогноз — "пальцем в небо".
justboris
Значит я не так понял исходное утверждение. То есть плохо то, что npm-модулям любят давать короткие имена…
Ну во-первых, в npm завезли scopes, свои служебные пакеты вы можете называть
@mycompany/package
, то есть ситуация как минимум не хуже composer.Во-вторых, чем длиннее имя, тем сложнее его запомнить. Понятно, что
org.apache.commons
помнят все, но вот какой-нибудьcom.squareup.retrofit2
илиio.reactivex.rxjava2
вряд ли, все равно будете гуглить сначала по названию, retrofit или rxjava соответственно, чтобы узнать полное имя.rjhdby
Современные IDE довольно умны и подобная проблема в принципе не возникает. Достаточно приблизительно помнить «имя» необходимого пакета, а корректный импорт IDE берет на себя.
justboris
Хорошо, решение есть. Но посмотрите на ситуацию со стороны: сначала придумали длинные уникальные имена, а потом потребовались утилиты, чтобы их не писать. Логично!
То есть получается, что у любой библиотеки все равно есть имя собственное, а приписанное сначала com.mycompany особой роли не играет
flancer Автор
Похоже, я ввёл вас в заблуждение, пытаясь продемонстрировать работу "пространства имён" на примере библиотек.
namespace
в PHP иpackage
в Java относятся к исходному коду, а не к библиотекам.Попробую запутать вас ещё больше. Всем известные интернетовские домены — это пример применения namespace'ов на практике.
Две разные библиотекидва разных домена с именемmail
одновременно существуют водном проектеинтернете именно потому, что они принадлежат двум разным пространствам имён:Пространство имён — некоторое множество… созданное для логической группировки уникальных идентификаторов…
На самом деле можно в интернете
проектеобойтись без доменов верхнего уровня и вообще без доменовnamespace'ови адресовать все хостыбиблиотеки"плоской" строкой. Это примерно то же самое, как хранить все файлы на компьютере в одном корневом каталоге. Да-да, файловая система — это тоже пример "логической группировки уникальных идентификаторов" (имён файлов) при помощи "некоторых множеств" (каталогов).И если на компьютере хранить 5-10 файлов, то вполне резонно задаваться вопросом, а для чего мне вообще нужны каталоги? И получить вполне логичный ответ — для хранения 5-10 файлов каталоги не нужны.
justboris
Дело в том, что у нас уже есть файловая система, и модули уже разложены по папкам и файлам. Зачем дополнительно вводить абстрактное пространство имен, если все и так организовано?
flancer Автор
В вашем проекте папки и файлы организованы так, в моём — по-другому, у кого-то — по третьему. Посмотрите содержимое файла
autoload_namespaces.php
, фрагмент которого я привёл в разделе "Автозагрузка кода". В нём видно, как файловая система каждого модуля (а сторонний модуль в вашем проекте с вашими правилами для кого-то является собственным проектом со своими правилами) монтируется в единую логическую структуру кода в вашем проекте. Вот, например, исходники модулей находятся в таких подкаталогах внутри каталога./vendor
вашего проекта:Но вы не заморачиваетесь путями к исходникам, а просто пишете в своём коде:
Поиск файла с исходниками автозагрузчик
autoload.php
выполнит самостоятельно, на основании привязки пространства\Prophecy\*
к каталогу./vendor/phpspec/prophecy/src
.Если ваш проект собран при помощи PHP Composer, то ваш код не изменится, если Konstantin Kudryashov (автор модуля
phpspec/prophecy
) захочет изменить структуру своего модуля и перенести исходники в своём модуле в каталог, например,./phpspec/prophecy/src/main/php
. Он просто укажет вcomposer.json
своего модуля:и при сборке или обновлении вашего проекта в следующий раз в карту пространства имен
autoload_namespaces.php
запишется так:С появлением
autoloader
'a PHP-разработчики перестали держать в голове файловую структуру своего проекта (и файловую структуру сторонних модулей, используемых в своём проекте) и стали держать логическую структуру кода (которая гораздо проще).Пространство имён не нужно, если
на компьютерев проектехранитсяучаствует 5-10файловразработчиков со своими модулями. Но еслина компьютерев проектехранитсяучаствует 1000файловразработчиков со своими модулями, то пространство имён становится более актуальным.justboris
Как-то дискуссия скачет с одного языка на другой, начали с Java, закончили на PHP. В Java пакетах, например, такой возможности нет, там вместо этого жесткие требования по расположению файлов на файловой системе.
Отсюда делаю вывод, что эта фича не является обязательной для достижения «максимальной сложности приложений».
flancer Автор
Пространства имён не привязаны к языку. В Java нет жестких требований по расположению файлов — вы указываете их местоположение в CLASSPATH при запуске приложения.
И пространства имён не являются обязательной фичей для достижения "максимальной сложности приложений". Я пытался донести мысль, что пространство имён при прочих равных условиях (например, язык и квалификация разработчика) позволяют создавать более сложные приложения, чем без них. Вы можете писать на PHP без использования namespace, некоторые до сих пор так и делают. Но в таких пакетах, как Zend2, Symfony, Laravel — используются namespaces.
Кстати, в Yii и CodeIgniter, к моему удивлению, не используют пространства имён. В результате в Yii имена файлов выглядят так:
А в CodeIgniter так:
Обратите внимание на префиксы в названиях файлов — разрабы вынуждены обеспечивать уникальность имени каждого файла в пределах проекта (по сути, вручную, не используя средств языка, вводят пространства
CDb
иDB_
). Да, для 1000 файлов они выкрутятся (какой ценой?), а для 300 млн.?Если вдруг решите попрограммировать на PHP, не начинайте с Yii и CodeIgniter ;)
SerafimArts
Первый Yii — это ныне мёртвая версия фреймворка 2008го года, тогда о неймспейсах не то что не слышали, но даже и не задумывались, а на дворе был PHP ~5.1. Смотрите лучше на Yii2 или Yii3.
flancer Автор
Да, троечка с namespace'ами. Вы реабилитировали Yii в моих глазах, спасибо :)
justboris
Это как раз для node.js не проблема. С большим количеством файлов их модульная система хорошо справляется. Настолько хорошо, что некоторые разработчики создают отдельные модули для самой тривиальной функциональности, порождая поводы для мемов.
rjhdby
Затем, что это совершенно разные вещи.
В автобусном парке есть два икаруса и один лиаз с номерами Н123С, Р778Т и М543Н.
С другой стороны, автобусный парк обслуживает маршруты 711, 215 и 987.
Даже не смотря на то, что мы можем уверенно сказать, что икарус Н123С обслуживает маршрут 711 — это все равно две разные, не связные между собой, плоскости абстракций.
justboris
Если следовать вашей аналогии, то можно сказать, что неймспейс и путь до файла с классом вообще никак не похожи. Но разве класс `\Prophecy\Call\CallCenter()` может внезапно смаппиться на файловую систему как `bla/bla/bla`?
flancer Автор
Сможет. Если прописать в
./vendor/composer/autoload_classmap.php
SerafimArts
Ээээ… Вы серьёзно предлагаете что-то трогать в папочке вендор? Или это просто для примера? Если для примера, то надо было что-то такое приводить в пример:
flancer Автор
Через
composer.json
оно, конечно, лучше, но я не стал плодить сущности в примере :) Тем более, что в конечном итоге оно всё равно окажется в./vendor/composer/autoload_classmap.php
. А так ваше замечание совершенно справедливо, в папке./vendor/composer
лучше не делать изменения ручками.SerafimArts
А вот и не факт. Ещё есть APCu и авторитативные карты классов.
Не то что «лучше»… А никогда и ни при каких обстоятельствах этого не делать. А если кто тронет — за это трогалку отчекрыживать.
Ну разве что только для отладки кода какого-нибудь можно трогать.