HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В динамических языках файлы с кодом могут выполнять две разных роли: быть исполняемым скриптом, либо быть модулем. В зависимости от роли на эти файлы накладываются разные ограничения, они по-разному устроены и ведут себя тоже по-разному.</p>
1 <p>В динамических языках файлы с кодом могут выполнять две разных роли: быть исполняемым скриптом, либо быть модулем. В зависимости от роли на эти файлы накладываются разные ограничения, они по-разному устроены и ведут себя тоже по-разному.</p>
2 <p>На проектах Хекслета студентам приходится писать и то и другое. При этом совершаются типовые ошибки, которые усложняют тестирование кода, его поддержку и расширяемость. Эта статья помогает разобраться с тем, что есть что, и как правильно организовывать код. Бонусом идет объяснение принципов создания утилит командной строки.</p>
2 <p>На проектах Хекслета студентам приходится писать и то и другое. При этом совершаются типовые ошибки, которые усложняют тестирование кода, его поддержку и расширяемость. Эта статья помогает разобраться с тем, что есть что, и как правильно организовывать код. Бонусом идет объяснение принципов создания утилит командной строки.</p>
3 <p>Примеры в этой статье приводятся на JavaScript, но язык не принципиален. Все эти особенности присущи и остальным динамическим языкам, таким как PHP, Python или Ruby.</p>
3 <p>Примеры в этой статье приводятся на JavaScript, но язык не принципиален. Все эти особенности присущи и остальным динамическим языкам, таким как PHP, Python или Ruby.</p>
4 <blockquote><p>Подписывайтесь на<a>канал Кирилла Мокевнина в Telegram</a>- чтобы узнать больше о программировании и профессиональном пути разработчика</p>
4 <blockquote><p>Подписывайтесь на<a>канал Кирилла Мокевнина в Telegram</a>- чтобы узнать больше о программировании и профессиональном пути разработчика</p>
5 </blockquote><h2>Содержание</h2>
5 </blockquote><h2>Содержание</h2>
6 <ul><li><a>Модуль</a></li>
6 <ul><li><a>Модуль</a></li>
7 <li><a>Скрипт</a></li>
7 <li><a>Скрипт</a></li>
8 <li><a>Библиотеки и утилиты командной строки</a></li>
8 <li><a>Библиотеки и утилиты командной строки</a></li>
9 </ul><h2>Модуль</h2>
9 </ul><h2>Модуль</h2>
10 <p>Начнем с терминологии. Модуль - это файл, содержащий определения функций, классов и других сущностей (в зависимости от языка). В разных языках модули называют разными словами, но суть от этого не меняется. Ниже пример модуля, содержащего класс User:</p>
10 <p>Начнем с терминологии. Модуль - это файл, содержащий определения функций, классов и других сущностей (в зависимости от языка). В разных языках модули называют разными словами, но суть от этого не меняется. Ниже пример модуля, содержащего класс User:</p>
11 <p>Сами по себе модули не являются законченными программами. Их нельзя (бессмысленно) выполнять напрямую, например, запустив в командной строке. Модули предназначены для использования другими модулями (или скриптами). Обычно в языках для этого есть либо механизм импортов, либо механизм автозагрузки, либо и то и другое. В JavaScript, чтобы получить доступ к определениям внутри какого-то модуля, его нужно импортировать:</p>
11 <p>Сами по себе модули не являются законченными программами. Их нельзя (бессмысленно) выполнять напрямую, например, запустив в командной строке. Модули предназначены для использования другими модулями (или скриптами). Обычно в языках для этого есть либо механизм импортов, либо механизм автозагрузки, либо и то и другое. В JavaScript, чтобы получить доступ к определениям внутри какого-то модуля, его нужно импортировать:</p>
12 <p>Частая ошибка, которую совершают новички при создании модулей, - выполнение кода вне определений. Например, так:</p>
12 <p>Частая ошибка, которую совершают новички при создании модулей, - выполнение кода вне определений. Например, так:</p>
13 <p>В какой момент вызовется эта функция? Она будет вызвана ровно в тот момент, когда этот файл будет загружен через импорт или автозагрузку.</p>
13 <p>В какой момент вызовется эта функция? Она будет вызвана ровно в тот момент, когда этот файл будет загружен через импорт или автозагрузку.</p>
14 <p>В некоторых языках так написать просто невозможно, например, в Java. Компилятор это просто не пропустит. В других языках такой код запрещен стандартами кодирования. Например, линтер в PHP выводит следующее предупреждение:</p>
14 <p>В некоторых языках так написать просто невозможно, например, в Java. Компилятор это просто не пропустит. В других языках такой код запрещен стандартами кодирования. Например, линтер в PHP выводит следующее предупреждение:</p>
15 <blockquote><p>A file SHOULD declare new symbols (classes, functions, constants, etc.) and cause no other side effects, or it SHOULD execute logic with side effects, but SHOULD NOT do both.</p>
15 <blockquote><p>A file SHOULD declare new symbols (classes, functions, constants, etc.) and cause no other side effects, or it SHOULD execute logic with side effects, but SHOULD NOT do both.</p>
16 </blockquote><p>Почему? Главная причина в непредсказуемости поведения. Код, определенный на уровне модуля (вне других функций), вызывается во время автозагрузки или при импорте. Причём не всегда можно точно сказать, где это происходит и сколько раз. Обычно за загрузку кода отвечает какой-либо фреймворк.</p>
16 </blockquote><p>Почему? Главная причина в непредсказуемости поведения. Код, определенный на уровне модуля (вне других функций), вызывается во время автозагрузки или при импорте. Причём не всегда можно точно сказать, где это происходит и сколько раз. Обычно за загрузку кода отвечает какой-либо фреймворк.</p>
17 <p>Сама загрузка происходит вне кода приложения, а значит, ошибки, возникающие на этом уровне, не смогут быть перехвачены приложением. Кроме того, это просто неожиданно. Обычный импорт приводит к тому, что запускаются какие-то внутренние процессы. Контролировать эту ситуацию невозможно.</p>
17 <p>Сама загрузка происходит вне кода приложения, а значит, ошибки, возникающие на этом уровне, не смогут быть перехвачены приложением. Кроме того, это просто неожиданно. Обычный импорт приводит к тому, что запускаются какие-то внутренние процессы. Контролировать эту ситуацию невозможно.</p>
18 <p>Помимо указанных особенностей подобный код часто выполняет побочные эффекты, меняет внутреннее состояние программы, например, глобальные переменные. Это значит, что после загрузки такого модуля у вас вдруг внезапно программа начинает вести себя по-другому. Особенно этим грешат Ruby и Javascript.</p>
18 <p>Помимо указанных особенностей подобный код часто выполняет побочные эффекты, меняет внутреннее состояние программы, например, глобальные переменные. Это значит, что после загрузки такого модуля у вас вдруг внезапно программа начинает вести себя по-другому. Особенно этим грешат Ruby и Javascript.</p>
19 <p><em>Чтобы быть до конца справедливым, иногда это оправдано.</em></p>
19 <p><em>Чтобы быть до конца справедливым, иногда это оправдано.</em></p>
20 <p>И последнее по списку, но не последнее по важности. Подобные вызовы могут блокировать возможность тестирования. Например, если на уровне модуля есть подобный вызов:</p>
20 <p>И последнее по списку, но не последнее по важности. Подобные вызовы могут блокировать возможность тестирования. Например, если на уровне модуля есть подобный вызов:</p>
21 <p>Такой файл будет невозможно загрузить в среду, где отсутствует document. И подмену тоже никак не сделать, так как модуль - это не функция, он не позволяет реализовать инверсию зависимостей.</p>
21 <p>Такой файл будет невозможно загрузить в среду, где отсутствует document. И подмену тоже никак не сделать, так как модуль - это не функция, он не позволяет реализовать инверсию зависимостей.</p>
22 <p>Если модуль написан правильно, то его безопасно включать в другие модули, и он может быть протестирован.</p>
22 <p>Если модуль написан правильно, то его безопасно включать в другие модули, и он может быть протестирован.</p>
23 <h2>Скрипт</h2>
23 <h2>Скрипт</h2>
24 <p>Что такое скрипт? Скрипт - это любой файл, который предназначен для запуска из командной строки. Он может быть<a>исполняемым</a>, но в общем случае это не обязательно:</p>
24 <p>Что такое скрипт? Скрипт - это любой файл, который предназначен для запуска из командной строки. Он может быть<a>исполняемым</a>, но в общем случае это не обязательно:</p>
25 <p>Этот скрипт содержит вызов функции, которая печатает на экран текущую дату:</p>
25 <p>Этот скрипт содержит вызов функции, которая печатает на экран текущую дату:</p>
26 <p>Сам скрипт запускается не напрямую, а через интерпретатор. Такой подход часто используется во время разработки.</p>
26 <p>Сам скрипт запускается не напрямую, а через интерпретатор. Такой подход часто используется во время разработки.</p>
27 <p>Когда скрипт "выпускается" для использования другими людьми, то наличие интерпретатора скрывают, а сам скрипт делают исполняемым. В некоторых языках, дополнительно, из названия исполняемого файла удаляют расширение. Это связано с тем, что для пользователей этого скрипта становится неважно, на каком языке он написан. В JavaScript этого делать не нужно, потому что имя скрипта не связано с названием файла.</p>
27 <p>Когда скрипт "выпускается" для использования другими людьми, то наличие интерпретатора скрывают, а сам скрипт делают исполняемым. В некоторых языках, дополнительно, из названия исполняемого файла удаляют расширение. Это связано с тем, что для пользователей этого скрипта становится неважно, на каком языке он написан. В JavaScript этого делать не нужно, потому что имя скрипта не связано с названием файла.</p>
28 <p>Запуск такого скрипта выглядит так:</p>
28 <p>Запуск такого скрипта выглядит так:</p>
29 <p>Чтобы сделать наш файл<em>date</em>исполняемым, нужно выполнить две задачи:</p>
29 <p>Чтобы сделать наш файл<em>date</em>исполняемым, нужно выполнить две задачи:</p>
30 <ol><li><p>Добавить права на исполнение: chmod +x date.</p>
30 <ol><li><p>Добавить права на исполнение: chmod +x date.</p>
31 </li>
31 </li>
32 <li><p>Добавить<a>шебанг</a>в начало файла:</p>
32 <li><p>Добавить<a>шебанг</a>в начало файла:</p>
33 <p>Такая запись внутри исполняемых файлов (скриптов) помогает командному интерпретатору, например, bash, подобрать правильный интерпретатор для запуска файла на исполнение.</p>
33 <p>Такая запись внутри исполняемых файлов (скриптов) помогает командному интерпретатору, например, bash, подобрать правильный интерпретатор для запуска файла на исполнение.</p>
34 </li>
34 </li>
35 </ol><p>Исполняемый файл предназначен только для прямого запуска. Это тупиковый файл. Его нельзя (технически можно, но лучше этого не делать) импортировать в другие модули. Он просто не предназначен для этого. Скрипты могут использовать модули, но модули не могут обращаться к скриптам.</p>
35 </ol><p>Исполняемый файл предназначен только для прямого запуска. Это тупиковый файл. Его нельзя (технически можно, но лучше этого не делать) импортировать в другие модули. Он просто не предназначен для этого. Скрипты могут использовать модули, но модули не могут обращаться к скриптам.</p>
36 <p><em>Python в этом аспекте пошел своим путем. Каждый модуль питона можно сделать скриптом, добавив специальное условие в конце файла. Это условие срабатывает только тогда, когда файл запускают как скрипт. При этом этот же файл можно использовать и как модуль без страха, что при импорте начнет выполняться какой-то код</em></p>
36 <p><em>Python в этом аспекте пошел своим путем. Каждый модуль питона можно сделать скриптом, добавив специальное условие в конце файла. Это условие срабатывает только тогда, когда файл запускают как скрипт. При этом этот же файл можно использовать и как модуль без страха, что при импорте начнет выполняться какой-то код</em></p>
37 <p>Что насчет тестирования? Из-за своей природы скрипты крайне плохо тестируются. С ними нельзя работать, как с обычным кодом. В тестах придётся запускать их как обычную программу и смотреть, что она делает через, например, анализ STDOUT.</p>
37 <p>Что насчет тестирования? Из-за своей природы скрипты крайне плохо тестируются. С ними нельзя работать, как с обычным кодом. В тестах придётся запускать их как обычную программу и смотреть, что она делает через, например, анализ STDOUT.</p>
38 <p>Из этого следует одно очень важное правило. Любой нетривиальный скрипт должен быть всего лишь способом запустить библиотечный код.</p>
38 <p>Из этого следует одно очень важное правило. Любой нетривиальный скрипт должен быть всего лишь способом запустить библиотечный код.</p>
39 <h2>Библиотеки и утилиты командной строки</h2>
39 <h2>Библиотеки и утилиты командной строки</h2>
40 <p>Такие пакеты, как eslint или babel, могут использоваться в двух режимах:</p>
40 <p>Такие пакеты, как eslint или babel, могут использоваться в двух режимах:</p>
41 <ol><li>Как программы, которые можно запускать из командной строки.</li>
41 <ol><li>Как программы, которые можно запускать из командной строки.</li>
42 <li>Как библиотеки, которые можно поставить в качестве зависимостей, импортировать в код и вызывать.</li>
42 <li>Как библиотеки, которые можно поставить в качестве зависимостей, импортировать в код и вызывать.</li>
43 </ol><p>При таком подходе, исполняемый файл (скрипт) становится<strong>клиентом</strong>библиотеки. Он не должен делать ничего, что библиотека должна делать сама. Только в этом случае библиотеку можно будет использовать где-то еще.</p>
43 </ol><p>При таком подходе, исполняемый файл (скрипт) становится<strong>клиентом</strong>библиотеки. Он не должен делать ничего, что библиотека должна делать сама. Только в этом случае библиотеку можно будет использовать где-то еще.</p>
44 <p>Это разделение резко упрощает тестирование. Так как логика сосредоточена внутри библиотеки, то с ней можно работать, как с обычным кодом.</p>
44 <p>Это разделение резко упрощает тестирование. Так как логика сосредоточена внутри библиотеки, то с ней можно работать, как с обычным кодом.</p>
45 <p>В свою очередь скрипт может иметь отдельную логику, которая не имеет отношения к библиотеке. К такой логике, например, относится парсинг аргументов командной строки: eslint -formatter json src. Эта часть приложения существует только тогда, когда с пакетом работают как с утилитой. Парсинг можно размещать напрямую в скриптах, либо выносить в отдельный модуль, который не связан напрямую с используемой библиотекой.</p>
45 <p>В свою очередь скрипт может иметь отдельную логику, которая не имеет отношения к библиотеке. К такой логике, например, относится парсинг аргументов командной строки: eslint -formatter json src. Эта часть приложения существует только тогда, когда с пакетом работают как с утилитой. Парсинг можно размещать напрямую в скриптах, либо выносить в отдельный модуль, который не связан напрямую с используемой библиотекой.</p>