Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fully qualified names vs imports #285

Closed
nin-jin opened this issue Oct 31, 2017 · 1 comment
Closed

Fully qualified names vs imports #285

nin-jin opened this issue Oct 31, 2017 · 1 comment

Comments

@nin-jin
Copy link
Member

nin-jin commented Oct 31, 2017

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

В 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'

В JS/TS по факту сейчас не работают.

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

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

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

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;
@nin-jin
Copy link
Member Author

nin-jin commented Nov 2, 2017

Как можно было бы зайдя на Гитхаб догадаться, что 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

@nin-jin nin-jin closed this as completed Jan 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant