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>