Skip to content

Fully Qualified Names vs Imports

Stefan edited this page Oct 22, 2021 · 4 revisions

Типы импортов

В JS и во многих других языках есть импорты двух видов: конкретные и массовые.

Конкретные импорты

Импортируют одно конкретное имя в локальную область видимости.

import Page from 'mol/book/Page'

или

import { Page } from 'mol/book'

или

import { BookPage as Page } from 'mol'

Такие импорты ни чем принципиально не отличаются от локальных алиасов:

const Page = $mol_book_page

Массовые импорты

Импортируют все имена из удалённого пространства имён в локальную область видимости.

import `mol/book'

Проблемы коротких имён

Потенциальные конфликты имён

Чем короче и абстрактнее имена, тем выше риск конфликтов. Приходится переименовывать импортируемые сущности. Зачастую в имена закладывают полную семантику, даже, когда она и так понятна из пути к модулю:

import { BookPage } from 'mol/book/Page'

Разное название одной сущности в разных контекстах

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

Синхронное изменение в нескольких местах файла

Начиная использовать сущность необходимо импортировать её в начале файла. Прекращая её использовать, нужно проверить, что нигде в файле она не используется и удалить импорт.

Необходимость вырезать лишнее при сборке

Актуально, когда импортируются не все сущности из внешнего модуля, то есть почти всегда. RollUp, tree shaking и тому подобные костыли.

Избыточно, когда место использования всего одно

Вместо того, чтобы писать:

import Page from 'mol/book/Page'
return new Page

Можно было бы просто написать:

return new $mol_book_page

В инструментах разработчика отображается короткое имя, а не полное

Если класс объявлен как:

export class Page { ... }

То в отладчике/профайлере/консоли отображаться будет именно это короткое имя. Если же использовать полные имена:

class $mol_book_page { ... }

То работа с инструментами разработчика становится куда приятней, так как не нужно гадать "который это Page из 5 и где находится".

Невозможно в рантайме определить полный путь.

Если класс/функция объявлен как:

class $mol_book_page { ... }

То, через свойство name можно получить полный путь, что можно использовать, например, для генерации глобально-уникальных человекопонятных css-классов в DOM:

[mol_page] { ... }
[mol_book_page] { ... }
[my_app_page] { ... }

Если же класс/функция объявлены лишь с коротким путём:

export class Page { ... }

То всё, что мы можем получить - это локальное имя, что достаточно бесполезно.

Нет простого доступа к области видимости внешнего модуля

Чтобы через консоль посмотреть состояние спрятанное в замыкании нужно поплясать с бубном.

Захламление исходников

Портянки импортов и экспортов могут занимать не один десяток строк. Типичный пример. Кроме того, появляется необходимость делать специальные "индексные модули", которые импортируют все модули из директирии и экспортят их как один объект.

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

Разные механизмы для разных языков

JavaScript импортируются по одним правилам, CSS по другим, шаблоны - третьим. Подключив скрипты нужно не забыть подключить стили, подключив стили - не забыть добавить деплой нужных им картинок и тп связанные вещи. А если стили завязаны на что-то типа modernizr - не забыть подключить соответствующий скрипт.

Вместо всей этой кутерьмы в $mol модулем является директория и все файлы внутри (на каких бы языках они ни были) включаются в соответствующие бандлы.

Попустительство бардаку в проекте

Сущность может называться Foo, а лежать в файле Bar. В общем случае по имени сущности не понять в каком файле она определена и где этот файл искать.

Достоинства коротких имён

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

import Page from 'mol/book/Page'
return new Page

Однако всегда можно сделать короткий локальный алиас, если в этом действительно есть необходимость:

const Page = $mol_book_page
return new Page

Интеграция с большинством новых библиотек

Так как import/export попали в стандарт ES, то многие уже вовсю пилят библиотеки используя эти конструкции. Соответственно воспользоваться такими библиотеками проще используя те же механизмы.

Примеры из других языков

PHP

Symfony

https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php

use Symfony\Component\Routing\RouteCollectionBuilder;
abstract protected function configureRoutes(RouteCollectionBuilder $routes);

Ребята перестарались с таксономией. Незачем делать такие глубокие иерархии и пытаться впихнуть в название половину её описания. Куда лаконичней смотрелось бы короткое, но полное имя:

abstract protected function configureRoutes(\Symfony\Routes $routes);

D

VibeD

http://vibed.org/api/vibe.http.router/URLRouter

import vibe.http.fileserver;
void addGroup(HTTPServerRequest req, HTTPServerResponse res)
router.get("/static/*", serveStaticFiles("public/"));
auto settings = new HTTPServerSettings;

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

void addGroup(.vibe.http.request req, .vibe.http.response res)
router.get("/static/*", .vibe.http.static.serve("public/"));
auto settings = new .vibe.http.server.settings;

Анализ большого проекта

Как можно было бы зайдя на Гитхаб догадаться, что NavigationExtras из Angular находится в https://github.com/angular/angular/blob/master/packages/router/src/router.ts ?

В начале файла мы видим типичную портянку из нескольких десятков инклудов.

Многие импорты являются тавтологиями:

import {createRouterState} from './create_router_state';

Зачем переименовывать snake_case в camelCase?

Не обошлось и без полного переименовывания:

import {ChildrenOutletContexts} from './router_outlet_context';

Стоит обратить внимание, что зачастую импортируются сложносоставные имена типа: DetachedRouteHandleInternal. Потому что в одно-два слова просто не удаётся впихнуть всю необходимую семантику.

Какие имена могли бы быть без импортов:

// import {Location} from '@angular/common';
$ng_location

// import {Compiler, Injector, NgModuleFactoryLoader, NgModuleRef, Type, isDevMode} from '@angular/core';
$ng_compiler , $ng_injector , $ng_loader , $ng_module , $ng_debug

// import {BehaviorSubject} from 'rxjs/BehaviorSubject';
$rx_behaviour

// import {Observable} from 'rxjs/Observable';
$rx_observable

// import {Subject} from 'rxjs/Subject';
$rx_subject

// import {Subscription} from 'rxjs/Subscription';
$rx_subscription

// import {of } from 'rxjs/observable/of';
$rx_of

// import {concatMap} from 'rxjs/operator/concatMap';
$rx_map_concat

// import {map} from 'rxjs/operator/map';
$rx_map

// import {mergeMap} from 'rxjs/operator/mergeMap';
$rx_map_merge

// import {applyRedirects} from './apply_redirects';
$ng_route_redirect

// import {LoadedRouterConfig, QueryParamsHandling, Route, Routes, validateConfig} from './config';
$ng_route_config , $ng_query_handling , $ng_route , $ng_route_list , $ng_validation

// import {createRouterState} from './create_router_state';
$ng_route_state

// import {createUrlTree} from './create_url_tree';
$ng_url_tree

// import {ActivationEnd, ChildActivationEnd, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
$ng_route_activated , $ng_route_child_ended , $ng_route_event , $ng_route_guarded , $ng_route_guarding , $ng_route_cancel , $ng_route_navigated , $ng_route_error , $ng_route_navigating , $ng_route_resolved , $ng_route_resolving , $ng_route_loaded , $ng_route_loading , $ng_route_recognized

// import {PreActivation} from './pre_activation';
$ng_route_activating

// import {recognize} from './recognize';
$ng_route_recognize

// import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
$ng_route_default , $ng_route_detached , $ng_route_strategy

// import {RouterConfigLoader} from './router_config_loader';
$ng_route_loader

// import {ChildrenOutletContexts} from './router_outlet_context';
$ng_route_outlet

// import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
$ng_route_active , $ng_route_snapshot_active , $ng_route_state , $ng_route_snapshot , $ng_route_active_advance , $ng_route_empty

// import {Params, isNavigationCancelingError} from './shared';
$ng_route_params , $ng_route_error_cancel

// import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
$ng_url_handling_default , $ng_url_handling

// import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
$ng_url_serializer , $ng_url_tree , $ng_url_tree_empty

// import {forEach} from './utils/collection';
$ng_each

// import {TreeNode, nodeChildrenAsMap} from './utils/tree';
$ng_tree_node , $ng_tree_dict