Настоящая реализация AMD гораздо умнее подходит к превращению имён модулей в URL и более надёжна, чем показано в примере. Проект RequireJS предоставляет популярную реализацию такого стиля загрузчика модулей.
Разработка интерфейса
Разработка интерфейсов – один из самых тонких моментов в программировании. Любую нетривиальную функциональность можно реализовать множеством способов. Поиск работающего способа требует проницательности и предусмотрительности.
Лучший способ познать значимость хорошего интерфейса – использовать много интерфейсов. Некоторые будут плохие, некоторые хорошие. Опыт покажет вам, что работает, а что – нет. Никогда не принимайте как должное плохой интерфейс. Исправьте его, или заключите в другой интерфейс, который лучше вам подходит.
Предсказуемость
Если программист может предсказать, как работает ваш интерфейс, ему не придётся часто отвлекаться и смотреть подсказку по его использованию. Постарайтесь следовать общепринятым соглашениям. Если есть модуль или часть языка JavaScript, которая делает что-то похожее на то, что вы пытаетесь реализовать – будет неплохо, если ваш интерфейс будет напоминать существующий. Таким образом, он будет привычен для людей, знакомых с существующим интерфейсом.
В поведении вашего кода предсказуемость также важна. Вас может постичь искушение сделать интерфейс слишком заумным якобы потому, что его удобнее использовать. К примеру, вы можете принимать любые виды типов и комбинаций аргументов и проделывать с ними «то, что надо». Или предоставлять десятки специализированных функций, которые предлагают незначительно отличающуюся функциональность. Это может сделать код, опирающийся на ваш интерфейс, немного короче, зато затруднить людям, работающим с ним, строить чёткую мысленную модель работы вашего модуля.
Компонуемость
Старайтесь использовать в интерфейсах настолько простые структуры данных, насколько это возможно. Делайте так, чтобы функции выполняли простые и понятные вещи. Если это применимо, делайте функции чистыми (см. Главу 3).
К примеру, частенько модули предлагают свою версию массивоподобных коллекций объектов со своим интерфейсом для подсчёта и извлечения элементов. У таких объектов нет методов map
или forEach
, и никакая функция, ожидающая настоящий массив, не сможет с ними работать. Это пример плохой компонуемости – модуль нельзя легко скомпоновать с другим кодом.
Примером может служить модуль для орфографической проверки текста, который может пригодиться в текстовом редакторе. Проверочный модуль можно сделать таким, чтобы он работал с любыми сложными структурами, используемыми самим редактором, и вызывал внутренние функции редактора для предоставления пользователю выбора вариантов написания. Если вы поступите таким образом, модуль нельзя будет использовать с другими программами. С другой стороны, если мы определим интерфейс модуля проверки, который принимает простую строку и возвращает позицию, на которой в строке есть возможная ошибка, а впридачу – массив предлагаемых поправок, тогда у нас будет интерфейс, который можно скомпоновать с другими системами, потому что строки и массивы всегда доступны в JavaScript.
Многослойные интерфейсы
Разрабатывая интерфейс для сложной системы (к примеру, отправка e-мейл), часто приходишь к дилемме. С одной стороны, не нужно перегружать пользователя интерфейса деталями. Не надо заставлять их изучать его 20 минут перед тем, как они смогут отправить e-мейл. С другой стороны, не хочется и прятать все детали – когда людям надо сделать что-то сложное при помощи вашего модуля, у них должна быть такая возможность.
Часто приходится предлагать два интерфейса: детализированный низкоуровневый для сложных ситуаций, и простой высокоуровневый для обычного использования. Второй можно построить на основе первого. В модуле для отправки e-мейлов высокоуровневый интерфейс может быть просто функцией, которая принимает сообщение, адрес получателя и отправителя, и отправляет письмо. Низкоуровневый должен давать доступ к заголовкам, приложенным файлам, HTML письмам и т. д.
Итог
Модули позволяют структурировать большие программы, разделяя код по разным файлам и пространствам имён. Если обеспечить их хорошо разработанными интерфейсами, их будет просто использовать, применять в других проектах и продолжать использовать при развитии и эволюции самого проекта.