Я программирую на PHP
. И немножко на JS
. Когда-то я программировал на Java
, ещё раньше — на LotusScript
. Попробовал на вкус python
и dart
. Basic
, Fortran
, Pascal
, Prolog
, VisualBasic
, С++
/С
, perl
— на всём этом я тоже изображал что-то исполняемое. Языки программирования меня интересуют с точки зрения создания компьютерных приложений. Web-приложений. Сложных web-приложений. Таких, которые пишут незнакомые друг с другом люди. Точнее, лично незнакомые — они знают друг друга по подписям в коммитах в общий репозиторий и по nickname’ам в баг-трекерах. Я не слишком умён, чтобы программировать на С
/С++
для различных ОС, и поэтому я программирую на PHP
для Magento.
Так, вот, возвращаясь к теме статьи, могу сказать, что пространство имён — один из очень важных столпов, на которых базируется написание сложных web-приложений группой слабознакомых друг с другом разработчиков.
В данном тексте под пространством имён я подразумеваю namespace с точки зрения PHP
, а не namespace с точки зрения python
’а:
<?php
namespace VendorProjectModuleComponentUnit;
Впервые с пространством имён я столкнулся при изучении 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
класс DoctrineDBALSchemaColumn
адресуется однозначно, каким бы образом не подключался исходный код к проекту. 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 ZendDbAdapter;
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
/....
Автор: Alex Gusev