awesome-everything EN
↑ Обратно к восхождению

Базовый CS с нуля

Модули

Суть Модуль группирует связанный код за границей. Он предоставляет публичный интерфейс — имена, которые он помечает как экспорты — и держит всё остальное приватным. Граница не даёт одной части программы залезать в другую.
◷ 20 min

Один объект связывает одну часть состояния с операциями над ней. Но реальная программа имеет десятки объектов, сотни функций и много вспомогательных значений. Если бы все они лежали в одном плоском пространстве, каждое имя было бы видно каждому другому имени. Любая функция могла бы прочитать или перезаписать любое значение. Опечатка в одном углу могла бы тихо сломать код в дальнем углу.

Программе такого размера нужна более крупная единица, чем объект, — способ провести линию вокруг группы связанного кода и сказать: внутри этой линии — одна вещь; снаружи можно пользоваться только тем, что я решу показать.

Эта линия и код, который она огораживает, — это модуль. Этот урок определяет, что такое модуль, что делает его граница и как он решает, какие из его имён остальной программе разрешено видеть.

Цель

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

1

Модуль: связанный код за границей. Модуль — это единица кода, которая группирует связанные определения — функции, объекты, значения — и проводит вокруг них границу.

Граница — это не физическая вещь. Это правило, которое язык обеспечивает: код по одну сторону границы не может свободно видеть имена по другую сторону. По умолчанию каждое имя, определённое внутри модуля, приватно для этого модуля — видно только другому коду внутри того же модуля.

Это расширяет идею, которую ты уже знаешь. В уроке 04 раздела 08 ты видел область видимости: переменная, объявленная внутри функции, видна только внутри этой функции. Модуль применяет тот же принцип на уровень выше — имя, определённое внутри модуля, по умолчанию видно только внутри этого модуля. Функция давала границу вокруг нескольких переменных; модуль даёт границу вокруг целой группы определений.

2

Экспорт: публичный интерфейс модуля. Если бы всё в модуле было приватным, никакой другой код никогда не смог бы этим пользоваться. Модуль был бы наглухо запечатан и бесполезен.

Поэтому модуль может пометить некоторые из своих имён как экспорты. Экспорт — это имя, которое модуль явно решает сделать видимым для кода вне своей границы. Всё, что не помечено как экспорт, остаётся приватным.

Набор экспортированных имён — это публичный интерфейс модуля, та же идея, что и интерфейс из урока 01, теперь в масштабе целого модуля. Внешний код может пользоваться экспортированными именами и больше ничем. Приватные имена — вспомогательные функции, внутренние значения, недоделанные части — это скрытая реализация модуля. Поэтому модуль — это абстракция: граница с выбранным интерфейсом снаружи и скрытой реализацией внутри.

export format()
pad()
TABLE
trim()
Один модуль, четыре определения. Экспортирована только format() (выделена) — это публичный интерфейс модуля. Остальные три (pad, TABLE, trim) приватны: они существуют и выполняются, но никакой код вне границы модуля не может их назвать.
3

Что предотвращает граница. Граница делает настоящую защитную работу. Поскольку внешний код может достичь модуля только через его экспорты, два сбоя становятся невозможными.

Во-первых, внешний код не может тронуть приватное состояние. Внутреннее значение модуля нельзя прочитать или перезаписать снаружи. Какой бы инвариант ни поддерживал модуль — счётчик, который должен оставаться неотрицательным, кеш, который должен оставаться согласованным — он в безопасности, потому что единственный код, который может его изменить, — это собственный код модуля.

Во-вторых, изменение приватного имени не может сломать внешний код. Если имя приватно, ничто снаружи от него не зависит, поэтому автор модуля может переименовать его, удалить или переписать свободно. Только экспортированные имена — это обязательство. Это снова правило из урока 01 — неизменный интерфейс, заменяемая реализация — но именно граница обеспечивает его: язык делает невозможным для внешнего кода случайно зависеть от приватной детали.

4

Пространства имён: почему имена перестают сталкиваться. Программе могут понадобиться две функции, обе разумно названные format — одна форматирует даты, другая форматирует валюту. В едином плоском пространстве два определения столкнулись бы: второе перезаписало бы первое.

Модуль даёт каждому имени дом. format модуля дат и format модуля валют — это разные имена, потому что каждое достигается через свой модуль: dateModule.format и currencyModule.format. Внешний код достигает экспортов модуля через имя модуля — именованную ручку — тем же синтаксисом точки, которым ты пользовался для полей объекта в Блоке 09. Имя модуля впереди держит их раздельно. Это разделение — имена, сгруппированные под модулем так, что одинаковые короткие имена не конфликтуют — называется пространством имён.

Пространства имён — это причина, по которой большая программа не остаётся без хороших имён. Каждый модуль — это свежее пространство; имя, использованное внутри одного модуля, ничего не говорит об имени внутри другого. Без них каждое имя в программе соперничало бы с каждым другим именем, и большие программы было бы невозможно писать.

Почему это работает

Зачем обеспечивать границу, а не просто доверять программистам? Команда могла бы договориться по соглашению «не трогать внутренности другого модуля» — но соглашения забываются, и под давлением сроков кто-нибудь всегда залезает за границу. Обеспеченная граница снимает вопрос полностью: приватное имя просто не видно, поэтому никто и не может от него зависеть — ни случайно, ни намеренно. Автор модуля тогда свободен менять каждую приватную деталь, точно зная, что ничто снаружи на неё не опирается. Граница превращает надежду в гарантию.

Разбор примера

Чтение небольшого модуля и поиск его интерфейса.

Вот модуль с четырьмя определениями:

// модуль: text-format
const TABLE = { ... };              // таблица поиска
function pad(s: string): string { ... }    // вспомогательная
function trim(s: string): string { ... }   // вспомогательная
export function format(s: string): string {
  return pad(trim(s));              // использует обе вспомогательные
}

Какие имена публичны? Ищи ключевое слово export. Оно есть только у format. Значит, публичный интерфейс модуля — ровно одно имя: format.

Какие имена приватны? Всё без export: TABLE, pad и trim — три приватных имени. Внешний код не может назвать ни одно из них.

Может ли внешний код вызвать pad? Нет. pad приватна. Код в другом модуле, который написал бы textFormat.pad("x"), упал бы — имя не экспортировано, поэтому оно не видно за границей.

Может ли автор переименовать TABLE в LOOKUP? Да, безопасно. TABLE приватна; ничто вне модуля не может на неё сослаться, поэтому переименование не может сломать никакой другой код. Только format — обязательство перед внешним миром.

Вывод: модуль предоставил одно тщательно выбранное имя и скрыл три. Пользователь модуля думает о format и больше ни о чём — ровно то сокращение, ради которого существует абстракция, теперь применённое к целой группе кода.

Частая ошибка

Распространённая ошибка — думать, что приватное имя не выполняется. Оно прекрасно выполняется. В примере pad и trim исполняются каждый раз, когда вызывается format, — они делают настоящую работу. «Приватный» не значит бездействующий; это значит не называемый снаружи модуля. Различие в видимости, а не в том, выполняется ли код.

Практика 0 / 5

Модуль определяет 6 имён. Он помечает 2 из них как экспорты. Сколько из его имён приватны?

Модуль text-format экспортирует только format. Сколько из его имён внешний код может вызвать напрямую?

Вспомогательная функция модуля приватна. Автор модуля переименовывает эту вспомогательную функцию. Сколько строк внешнего кода должно измениться из-за переименования?

В модуле A есть функция с именем format. В модуле B тоже есть функция с именем format. Достигаемые как A.format и B.format, сталкиваются ли эти два имени? Введи 1 за «да», 0 за «нет».

Модуль держит приватный счётчик, который никогда не должен опускаться ниже 0. Сколько мест во всей программе могут изменить значение этого счётчика?

Проверь себя
Викторина

Что такое модуль и что делает его граница?

Итог

Модуль — это единица кода, которая группирует связанные определения за границей. По умолчанию каждое имя внутри модуля приватно — видно только другому коду в том же модуле, так же как переменная ограничена своей функцией. Модуль помечает выбранные имена как экспорты; набор экспортированных имён — это его публичный интерфейс, а всё остальное — скрытая реализация. Граница делает настоящую работу: внешний код не может прочитать или перезаписать приватное состояние модуля, а изменение приватного имени не может сломать никакой код снаружи, поэтому автор может свободно переписывать внутренности. Группировка имён под модулем также даёт каждому имени дом — пространство имён — так что два модуля могут оба использовать короткое имя вроде format, не сталкиваясь. Модуль — это абстракция, применённая к целой группе кода: выбранный интерфейс снаружи, защищённая реализация внутри.

Продолжить восхождение ↑Зачем существует абстракция
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.