Марейн Хавербеке - Выразительный JavaScript
"."].forEach(function(s) {
if (number.test(s))
console.log("Неправильно принято '" + s + "'");
});
10. Модули
Начинающий программист пишет программы так, как муравьи строят муравейник – по кусочку, без размышления над общей структурой. Его программы как песок. Они могут недолго простоять, но вырастая, они разваливаются.
Поняв проблему, программист тратит много времени на размышления о структуре. Его программы получаются жёстко структурированными, как каменные изваяния. Они тверды, но когда их нужно менять, над ними приходится совершать насилие.
Мастер-программист знает, когда нужна структура, а когда нужно оставить вещи в простом виде. Его программы словно глина – твёрдые, но податливые.
Мастер Юан-Ма, «Книга программирования»У каждой программы есть структура. В частностях она определяется тем, как программист делит код на функции и блоки внутри этих функций. Программисты вольны в создании структуры своей программы. Структура определяется больше вкусом программиста, нежели функциональностью программы.
В случае больших программ отдельные функции уже теряются в коде, и нам необходима единица организации кода больших масштабов. Модули группируют программный код по каким-то определённым признакам. В этой главе мы рассмотрим преимущества такого деления и техники создания модулей в JavaScript.
Зачем нужны модули
Есть несколько причин, по которым авторы делят свои книги на главы и секции. Это помогает читателю понять, как построена книга, и найти нужные им части. Автору это помогает концентрироваться на каждой конкретной части. Преимущества организации программ в нескольких файлах, или модулях, примерно те же. Структуризация помогает незнакомым с кодом людям найти то, что им нужно, и помогает программистам хранить связанные друг с другом вещи в одном месте.
Некоторые программы организованы по модели обычного текста, где порядок следования чётко определён, и где читателю предлагается последовательное изучение программы и множество прозы (комментариев) для описания кода. Это делает чтение кода менее пугающим (а чтение чужого кода обычно пугает), но требует больших усилий при создании кода. Также такую программу сложнее менять, потому что части прозы связаны между собой сильнее, чем части кода. Этот стиль называется литературным программированием. Те главы книги, в которых обсуждаются проекты, можно считать литературным кодом.
Обычно структурирование чего-либо требует затрат энергии. На ранних стадиях проекта, когда вы ещё не уверены, что где будет, и какие модули вообще нужны, я пропагандирую бесструктурную минималистическую организацию кода. Просто размещайте все части там, где удобно, пока код не стабилизируется. Таким образом не придётся тратить время на перестановку кусков программы, и вы не поймаете себя в такую структуру, которая не подходит для вашей программы.
Пространство имён
У большинства современных ЯП есть промежуточные области видимости (ОВ) между глобальной (видно всем) и локальной (видно только этой функции). У JavaScript такого нет. По умолчанию, всё, что необходимо видеть снаружи функции верхнего уровня, находится в глобальной ОВ.
Загрязнение пространства имён (ПИ), когда не связанные друг с другом части кода делят один набор переменных, упоминалась в главе 4. Там объект Math был приведён в качестве примера объекта, который группирует функциональность, связанную с математикой, в виде модуля.
Хотя JavaScript не предлагает непосредственно конструкции для создания модуля, мы можем использовать объекты для создания подпространств имён, доступных отовсюду. А функции можно использовать для создания изолированных частных пространств имён внутри модуля. Чуть дальше мы обсудим способ построения достаточно удобных модулей, изолирующих ПИ при помощи базовых концепций языка.
Повторное использование
В проекте, не разбитом на модули, непонятно, какие части кода необходимы для конкретной функции. В моей программе, шпионящей за врагами (глава 9), я написал функцию чтения файлов с настройками. Если мне понадобится использовать её в другом проекте, я должен буду скопировать части старой программы, которые вроде бы связаны с этой функцией, в мой новый проект. А если я найду там ошибку, я её исправлю только в том проекте, над которым работаю в данный момент, и забуду исправить её во всех остальных.
Когда у вас будет множество таких сдублированных кусков кода, вы обнаружите, что тратите кучу времени и сил на их копирование и обновление. Если разместить связанные между собой части программ в отдельные модули, их будет проще отслеживать, исправлять и обновлять, потому что везде, где этот функционал потребуется, вы сможете просто загрузить этот модуль из файла.
Эту идею можно использовать ещё лучше, если чётко прописать взаимоотношения разных модулей – кто от кого зависит. Тогда можно автоматизировать процесс установки и обновления внешних модулей (библиотек).
Если ещё развить идею – представьте себе онлайн-сервис, который отслеживает и распространяет сотни тысяч таких библиотек, и вы можете искать нужную вам функциональность среди них, а когда найдёте – ваш проект автоматически скачает её.
И такой сервис есть! Он называется NPM (npmjs.org). NPM – онлайн-база модулей и инструмент для скачивания и апгрейда модулей, от которых зависит ваша программа. Он вырос из Node.js, окружения JavaScript, не требующего браузера, которое мы обсудим в главе 20, но также может использоваться и в браузерных программах.
Устранение связей (Decoupling)
Ещё одна задача модулей – изолировать несвязанные между собой части кода так, как это делают интерфейсы объектов. Хорошо продуманный модуль предоставляет интерфейс для его использования вовне. Когда модуль обновляют или исправляют, существующий интерфейс остаётся неизменным, чтобы другие модули могли использовать новую, обновлённую версию без изменений в них самих.
Стабильный интерфейс не означает, что в него не добавляют новые функции, методы или переменные. Главное, что существующая функциональность не удаляется и её смысл не меняется. Хороший интерфейс позволяет модулю расти, не ломая старый интерфейс. А это значит – выставлять наружу как можно меньше внутренней кухни модуля, при этом язык интерфейса должен быть достаточно гибким и мощным для применения в различных ситуациях.
Интерфейсы, выполняющие простую задачу, вроде чтения настроек из файла, выходят такими естественным образом. Для других – к примеру, для редактора текстов, у которого есть множество разных аспектов, требующих доступа извне (содержимое, стили, действия пользователя и т.п.) интерфейс необходимо скрупулёзно продумывать.
Использование функций в качестве пространств имён
Функции – единственная вещь в JavaScript, создающая новую область видимости. Если нам нужно, чтобы у модулей была своя область видимости, придётся основывать их на функциях.
Обратите внимание на простейший модуль, связывающий имена с номерами дней недели – как делает метод getDay объекта Date.
var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
function dayName(number) {
return names[number];
}
console.log(dayName(1));
// → Вторник
Функция dayName – часть интерфейса модуля, а переменная names – нет. Но хотелось бы не загрязнять глобальное пространство имён.
Можно сделать так:
var dayName = function() {
var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
return function(number) {
return names[number];
};
}();
console.log(dayName(3));
// → Четверг
Теперь names – локальная переменная безымянной функции. Функция создаётся и сразу вызывается, а её возвращаемое значение (уже нужная нам функция dayName) хранится в переменной. Мы можем написать много страниц кода в функции, объявить там сотню переменных, и все они будут внутренними для нашего модуля, а не для внешнего кода.
Подобный шаблон можно использовать для изолирования кода. Следующий модуль пишет в консоль значение, но не предоставляет никаких значений для использования другими модулями.
(function() {
function square(x) { return x * x; }
var hundred = 100;
console.log(square(hundred));
})();
// → 10000
Этот код выводит квадрат сотни, но в реальности это мог бы быть модуль, добавляющий метод к какому-то прототипу, или настраивающий виджет на веб-странице. Он обёрнут в функцию для предотвращения загрязнения глобальной ОВ используемыми им переменными.