Введение
Модульное программирование — это методика разработки программного обеспечения, которая делает акцент на разделении функциональности программы на независимые, взаимозаменяемые модули, каждый из которых содержит все необходимое для реализации только одного аспекта требуемой функциональности. Интерфейс модуля определяет элементы, которые он предоставляет и требует. Элементы, определенные в интерфейсе, доступны для использования другими модулями. Реализация содержит рабочий код, соответствующий элементам, объявленным в интерфейсе. Модульное программирование тесно связано со структурированным и объектно-ориентированным программированием, все они преследуют общую цель — упростить создание крупных программ и систем путем их декомпозиции на более мелкие части, и все они возникли примерно в 1960-х годах. Несмотря на непоследовательность в историческом использовании этих терминов, в настоящее время под "модульным программированием" понимают декомпозицию кода всей программы на части высокого уровня; структурированное программирование — использование структурированных конструкций управления потоком выполнения на низком уровне кода, а объектно-ориентированное программирование — использование данных объектов, представляющих собой разновидность структуры данных. В объектно-ориентированном программировании использование интерфейсов как архитектурного подхода к построению модулей известно как программирование на основе интерфейсов.
Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality. A module interface expresses the elements that are provided and required by the module. The elements defined in the interface are detectable by other modules. The implementation contains the working code that corresponds to the elements declared in the interface. Modular programming is closely related to structured programming and object oriented programming, all having the same goal of facilitating construction of large software programs and systems by decomposition into smaller pieces, and all originating around the 1960s. While the historical usage of these terms has been inconsistent, "modular programming" now refers to the high level decomposition of the code of an entire program into pieces: structured programming to the low level code use of structured control flow, and object oriented programming to the data use of objects, a kind of data structure. In object oriented programming, the use of interfaces as an architectural pattern to construct modules is known as interface based programming.
История
Модульное программирование в виде подсистем (особенно для ввода-вывода) и программных библиотек восходит к ранним программным системам, где оно использовалось для повторного использования кода. Модульное программирование как таковое, с целью модульности, развивалось в конце 1960-х и 1970-х годов как более масштабный аналог концепции структурированного программирования (1960-е годы). Термин "модульное программирование" датируется, по крайней мере, Национальным симпозиумом по модульному программированию, организованным в Институте информации и систем в июле 1968 года Ларри Константином; другими ключевыми концепциями были сокрытие информации (1972) и разделение ответственности (SoC, 1974). Модули не были включены в оригинальную спецификацию ALGOL 68 (1968), но были добавлены в качестве расширений в ранних реализациях, ALGOL 68 R (1970) и ALGOL 68C (1970), а затем формализованы. Одним из первых языков, разработанных с самого начала для модульного программирования, был недолговечный Modula (1975) от Никлауса Вирта. Другим ранним модульным языком был Mesa (1970-е годы), разработанный Xerox PARC, и Вирт использовал Mesa, а также оригинальную Modula при создании своего преемника, Modula 2 (1978), который повлиял на более поздние языки, особенно через своего преемника, Modula 3 (1980-е годы). Использование Modula точечных имен, таких как M.a для ссылки на объект a из модуля M, совпадает с обозначением для доступа к полю записи (и аналогично для атрибутов или методов объектов) и сейчас широко распространено, например, в C#, Dart, Go, Java, OCaml и Python. Модульное программирование получило широкое распространение с 1980-х годов: оригинальный язык Pascal (1970) не включал модули, но более поздние версии, в частности UCSD Pascal (1978) и Turbo Pascal (1983), включали их в виде "юнитов", как и Pascal, повлиявший на Ada (1980). Расширенный стандарт ISO 10206:1990 для Pascal оставался ближе к Modula2 в плане поддержки модулей. Standard ML (1984) обладает одной из наиболее полных модульных систем, включая функторы (параметризованные модули) для отображения между модулями. В 1980-х и 1990-х годах модульное программирование было затмено и часто смешивалось с объектно-ориентированным программированием, особенно из-за популярности C++ и Java. Например, семейство языков C получило поддержку объектов и классов в C++ (первоначально C с классами, 1980) и Objective C (1983), а поддержку модулей получило только через 30 и более лет. Java (1995) поддерживает модули в виде пакетов, хотя основной единицей организации кода является класс. Однако Python (1991) с самого начала активно использовал как модули, так и объекты, используя модули в качестве основной единицы организации кода и "пакеты" как единицу более крупного масштаба; а Perl 5 (1994) включает поддержку как модулей, так и объектов, с огромным количеством модулей, доступных из CPAN (1993). OCaml (1996) последовал за ML, поддерживая модули и функторы. Модульное программирование сейчас широко распространено и встречается практически во всех основных языках, разработанных с 1990-х годов. Относительная важность модулей варьируется в зависимости от языка, и в объектно-ориентированных языках, основанных на классах, все еще существует пересечение и путаница с классами как единицей организации и инкапсуляции, но обе эти концепции хорошо установлены как отдельные.
Терминология
Вместо модуля иногда используется термин сборка (как в языках .NET, таких как C#, F# или Visual Basic .NET) или пакет (как в Dart, Go или Java). В других реализациях это различные понятия: в Python пакет представляет собой набор модулей, а в Java 9 была введена новая концепция модуля (набор пакетов с расширенным контролем доступа). Кроме того, термин "пакет" имеет и другие значения в области программного обеспечения (например, пакеты NuGet для .NET). Компонент – это схожая концепция, но обычно относится к более высокому уровню абстракции: компонент – это часть целой системы, в то время как модуль – это часть отдельной программы. Масштаб понятия "модуль" существенно различается в разных языках: в Python он очень мал, и каждый файл является модулем, а в Java 9 планируется использовать более крупный масштаб, где модуль представляет собой набор пакетов, которые, в свою очередь, состоят из набора файлов. Другие термины, используемые для обозначения модулей, включают в себя "unit" (единица), применяемый в диалектах Паскаля.
Языковая поддержка
Языки, которые официально поддерживают концепцию модулей, включают Ada, ALGOL, BlitzMax, C++, C#, Clojure, COBOL, Common Lisp, D, Dart, eC, Erlang, Elixir, Elm, F, F#, Fortran, Go, Haskell, IBM/360 Assembler, Control Language (CL), IBM RPG, Java, Julia, MATLAB, ML, Modula, Modula 2, Modula 3, Morpho, NEWP, Oberon, Oberon 2, Objective C, OCaml, несколько производных Pascal (Component Pascal, Object Pascal, Turbo Pascal, UCSD Pascal), Perl, PHP, PL/I, PureBasic, Python, R, Ruby, Rust, JavaScript, Visual Basic (.NET) и WebDNA. Яркими примерами языков, не поддерживающих модули, являются C и Pascal в их первоначальной форме. Однако C и C++ позволяют осуществлять раздельное компилирование и определять декларативные интерфейсы с использованием файлов заголовков. Модули были добавлены в Objective C в iOS 7 (2013); в C++ с C++20, а Pascal был вытеснен Modula и Oberon, которые изначально включали поддержку модулей, а также различными производными, которые её предоставляли. JavaScript имеет встроенные модули с ECMAScript 2015. Модульное программирование можно выполнять даже в тех языках, которым не хватает явных синтаксических средств для поддержки именованных модулей, например, в C. Это достигается за счет использования существующих возможностей языка, а также, например, соглашений о кодировании, идиом программирования и физической структуры кода. IBM i также использует модули при программировании в интегрированной языковой среде (ILE).
Основные аспекты
При модульном программировании задачи разделяются таким образом, что модули выполняют логически обособленные функции, взаимодействуя через чётко определённые интерфейсы. Часто модули образуют направленный ациклический граф (DAG); в этом случае циклическая зависимость между модулями указывает на то, что их следует объединить в один модуль. Если модули образуют DAG, они могут быть организованы в иерархию, где модули нижнего уровня независимы и не зависят от других модулей, а модули верхнего уровня зависят от модулей нижнего уровня. Отдельная программа или библиотека является модулем верхнего уровня своей иерархии, но в то же время может рассматриваться как модуль нижнего уровня в более крупной программе, библиотеке или системе. При создании модульной системы вместо разработки монолитного приложения (где наименьший компонент – это целое), пишутся несколько небольших модулей, которые при объединении формируют исполняемую программу. Обычно они также компилируются отдельно, посредством раздельной компиляции, а затем связываются компоновщиком. Компилятор JIT может выполнять часть этого процесса "на лету" во время выполнения. Эти независимые функции обычно классифицируются как функции управления программой или функции конкретных задач. Функции управления программой предназначены для работы в рамках одной программы. Функции конкретных задач тщательно разрабатываются для применения в различных программах. Это делает правильно спроектированные модульные системы гораздо более пригодными для повторного использования, чем традиционные монолитные, поскольку все (или многие) из этих модулей могут быть повторно использованы (без изменений) в других проектах. Это также упрощает разделение проектов на несколько более мелких. Теоретически, модульный программный проект легче собирать большими командами, поскольку ни один из участников не создаёт всю систему и даже не обязан знать о ней целиком. Они могут сосредоточиться только на своей небольшой задаче.