Кое-что о пространстве имён

в 19:22, , рубрики: javascript, namespaces, php, Программирование, сложные системы, языки программирования

Я программирую на 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), достаточно легко ориентироваться:

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 класс DoctrineDBALSchemaColumn адресуется однозначно, каким бы образом не подключался исходный код к проекту. 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 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js