Число модулей в модульной программе определяется. Что такое модульное программирование и кому оно нужно. Дополнительное время, расходуемое на вычисление действительного адреса в теле модуля, может привести к замедлению выполнения конкретного модуля, чем у

Структурное программирование

Самое общеизвестное определение структурного программирования – подход к программированию, в котором для передачи управления в программе используется три конструкции: следование, выбора и цикл.

Классическая теорема Боэма и Джакопини о структурном программировании утверждает, что всякую правильную программу (т. е. программу с одним входом и одним выходом, без зацикливаний и недостижимых веток) можно записать с использованием следующих логических структур:

последовательности двух или более операторов;

дихотомического выбора;

повторения;

Дейкстра предложил отказаться от оператора безусловного перехода и ограничиться тремя конструкциями – последовательностью, выбором и циклом;

Дональд Кнут продемонстрировал случаи, в которых оператор безусловного перехода оказывался полезным (например, выход из нескольких вложенных циклов) и подверг критике утверждение Дейкстры.

В 1965 году академик Глушков обратил внимание на то, что структурированные программы можно рассматривать как формулы в некоторой алгебре. Зная правила преобразования выражений в такой алгебре, можно осуществлять глубокие формальные (и, следовательно, автоматизированные) преобразования программ.

Структурное программирование – не самоцель, его основное назначение – получение хорошей программы. Однако даже в самой хорошей программе операторы перехода требуются, например при выходе из множества вложенных циклов.

Модульное программирование

Модульное программирование – это такой способ программирования, при котором вся программа разбивается на группу компонентов, называемых модулями, причем каждый из них имеет свой контролируемый размер, четкое назначение и детально проработанный интерфейс с внешней средой. Единственная альтернатива модульности – монолитная программа, что, конечно, неудобно. Таким образом, наиболее интересный вопрос при изучении модульности – определение критерия разбиения на модули. В основе модульного программирования лежат три основные концепции.

Принцип утаивания информации . Всякий компонент утаивает единственное проектное решение, т. е. модуль служит для утаивания информации. Подход к разработке программ заключается в том, что сначала формируется список проектных решений, которые особенно трудно принять или которые, скорее всего, будут меняться. Затем определяются отдельные модули, каждый из которых реализует одно из указанных решений.

Аксиома модульности . Модуль – независимая программная единица, служащая для выполнения некоторой определенной функции программы и для связи с остальной частью программы. Программная единица должна удовлетворять следующим условиям:


– блочность организации, т. е. возможность вызвать программную единицу из блоков любой степени вложенности;

– синтаксическая обособленность, т. е. выделение модуля в тексте синтаксическими элементами;

– семантическая независимость, т. е. независимость от места, где программная единица вызвана;

– общность данных, т. е. наличие собственных данных, сохраняющихся при каждом обращении;

– полнота определения, т. е. самостоятельность программной единицы.

Сборочное программирование . Модули – это программные кирпичи, из которых строится программа.

Сцепление модулей – мера относительной независимости модуля от других модулей. Независимые модули могут быть модифицированы без переделки других модулей. Чем слабее сцепление модуля, тем лучше. Рассмотрим различные типы сцепления.

– независимые модули – это идеальный случай. Модули ничего не знают друг о друге. Организовать взаимодействие таких модулей можно, зная их интерфейс и соответствующим образом перенаправив выходные данные одного модуля на вход другого.

– сцепление по данным (параметрическое) - это сцепление, когда данные передаются модулю, как значения его параметров, либо как результат его обращения к другому модулю для вычисления некоторой функции. Этот вид сцепления реализуется в языках программирования при обращении к функциям (процедурам).

Рутинность модуля – это независимость модуля от предыдущих обращений к нему (от предыстории). Модуль является рутинным, если результат его работы зависит только от количества переданных параметров (а не от количества обращений). Модуль должен быть рутинным в большинстве случаев, но есть и случаи, когда модуль должен сохранять историю. В выборе степени рутинности модуля пользуются тремя рекомендациями:

– в большинстве случаев делаем модуль рутинным;

– зависящие от предыстории модули следует использовать только в тех случаях, когда это необходимо для сцепления по данным;

– в спецификации зависящего от предыстории модуля должна быть четко сформулирована эта зависимость, чтобы пользователи имели возможность прогнозировать поведение такого модуля.

ТЕХНОЛОГИИ ПРОЕКТИРОВАНИЯ ПРОГРАММ

Введение

Структурное проектирование

Нисходящее проектирование

Модульное программирование

Структурное программирование

2. Объектно-ориентированное проектирование

2.1. Основные понятия объектно-ориентированного проектирования

2.2. Пример объектно-ориентированного проектирования

ЛИТЕРАТУРА

1. Марченко А.И. , Марченко Л.А. Программирование в среде Turbo Pascal 7.0. – 8-е изд. – К.: ВЕК+, СПб.: КОРОНА принт, 2004. с. 232-238.

2. Ставровский А.Б. Первые шаги в программировании. Самоучитель. – М.: «Вильямс», 2003. с. 113-133.

3. Вирт Н. Алгоритмы и структуры данных. – М.: Мир, 1989.

4. Иванова Г.С. Технология программирования : Учебник для вузов. – М.: Изд-во МГТУ им. Н.Э.Баумана, 2002. -320 с.

Введение

Промышленный подход к разработке программных продуктов породил ряд современных технологий проектирования алгоритмов и программ , среди которых наибольшее распространение получили:

· структурное проектирование программных продуктов;

· информационное моделирование предметной области

и связанных с ней приложений;

· объектно-ориентированное проектирование программных продуктов и др.

Целью данного занятия является изучение основных принципов структурного и объектно-ориентированного проектирования программ


Структурное проектирование

В основе технологии структурного проектирования лежит последовательная декомпозиция, целенаправленное структурирование задачи на отдельные составляющие.

Методы структурного проектирования представляют собой комплекс технических и организационных принципов системного проектирования.

Типичными методами структурного проектирования являются:

· нисходящее проектирование, кодирование и тестирование программ;

· модульное программирование;

· структурное программирование и др.

В зависимости от объекта структурирования различают:

· функционально-ориентированные методы - последовательное разложение задачи или целостной проблемы на отдельные, достаточно простые составляющие, обладающие функциональной определенностью;

· методы структурирования данных .

Для функционально-ориентированных методов в первую очередь учитываются заданные функции обработки данных, в соответствии с которыми определяется состав и логика работы (алгоритмы) отдельных компонентов программного продукта. С изменением содержания функций обработки, их состава, соответствующего им информационного входа и выхода требуется перепроектирование программного продукта. Основной упор в структурном подходе делается на моделирование процессов обработки данных.

Для методов структурирования данных осуществляется анализ, структурирование и создание моделей данных, применительно к которым устанавливается необходимый состав функций и процедур обработки. Программные продукты тесно связаны со структурой обрабатываемых данных, изменение которой отражается на логике обработки (алгоритмах) и обязательно требует перепроектирования программного продукта.

Структурный подход использует:

· диаграммы потоков данных (информационно-технологические схемы) – показывают процессы и информационные потоки между ними с учетом событий, инициирующих процессы обработки;

· интегрированную структуру данных предметной области (инфологическая модель, ER-диаграммы);

· диаграммы декомпозиции – структура и декомпозиция целей, функций управления, приложений;

· структурные схемы – архитектура программного продукта в виде иерархии взаимосвязанных программных модулей с идентификацией связей между ними, детальная логика обработки данных программных модулей (блок-схемы).


Нисходящее проектирование

Спецификация задачи служит отправной точкой в создании программы. Нужно понять, какие действия должны быть совершены для решения задачи, описать их на естественном языке и на достаточно высоком уровне абстракции. В программировании уже давно используются специальные языки - языки формальных спецификаций . Однако их изучение требует определенной подготовки. Поэтому ограничимся неформальными спецификациями , но как можно более точными и полными.

Спецификация задачи является ее первичным проектом . От него мы движемся к программе, постепенно уточняя описание.

Постепенное уточнение проектов широко используется во многих отраслях инженерной деятельности и называется методом проектирования сверху вниз (пошаговой детализации или нисходящего проектирования ).

В качестве примера рассмотрим проект одевания ребенка.

Первичная цель :

Конкретизация цели на первом шаге :

Одеть нижнюю половину.

Одеть верхнюю половину.

Нижнюю половину можно одеть в два этапа:

Надеть брюки.

Надеть носки и ботинки.

Верхнюю половину можно также одеть в два этапа:

Надеть рубашку.

Надеть куртку.

Окончательный проект выглядит так:

Надеть брюки.

Надеть носки.

Надеть ботинки.

Надеть рубашку.

Надеть куртку.

Метод нисходящего проектирования предполагает последовательное разложение общей функции обработки данных на простые функциональные элементы («сверху-вниз»). В результате строится иерархическая схема , отражающая состав и взаимоподчиненность отдельных функций, которая носит название функциональная структура алгоритма (ФСА ) (рис. 1.1).

Последовательность действий по разработке ФСА приложения следующая:

1) определяются цели автоматизации предметной области и их иерархия (цель-подцель);

2) устанавливается состав приложений (задач обработки), обеспечивающих реализацию поставленных целей;

3) уточняется характер взаимосвязи приложений и их основные характеристики (информация для решения задач, время и периодичность решения, условия выполнения и др.);

4) определяются необходимые для решения задач функции обработки данных ;

5)
выполняется декомпозиция функций обработки до необходимой структурной сложности, реализуемой предполагаемым инструментарием.

Подобная структура приложения отражает наиболее важное – состав и взаимосвязь функций обработки информации для реализации приложений, хотя и не раскрывает логику выполнения каждой отдельной функции, условия или периодичность их вызовов.

Разложение должно носить строго функциональный характер , т.е. отдельный элемент ФСА описывает законченную содержательную функцию обработки информации, которая предполагает определенный способ реализации на программном уровне.

По частоте использования функции обработки делятся на:

· однократно выполняемые;

· повторяющиеся.

Степень детализации функций может быть различной, но иерархическая схема должна давать представление о составе и структуре взаимосвязанных функций и общем алгоритме обработки данных. Широко используемые функции приобретают ранг стандартных (встроенных) функций при проектировании внутренней структуры программного продукта.

Уточнение действий при нисходящем проектировании - это, по сути, переход от описания того, что нужно сделать, к тому, как это сделать.

При уточнении действий в процессе проектирования программа разбивается на систему подпрограмм и программных единиц, а также конкретизируется представление данных.

В программировании также применяется метод последовательной модернизации . Сначала проектируется и реализуется упрощенный вариант решения задачи - прототип (однако и для него применяется нисходящее проектирование). Затем спецификации постепенно усложняются, а программа наращивается с соответствующим расширением возможностей, пока не будет получен окончательный вариант.

Модульное программирование

Модульное программирование является естественным следствием проектирования сверху вниз и заключается в том, что программа разбивается на части – модули , разрабатываемые по отдельности. В программировании под модулем понимается отдельная подпрограмма , а подпрограммы часто называются процедурами или процедурами-функциями . Поэтому модульное программирование еще называется процедурным .

Модуль должен обладать следующими свойствами :

· один вход и один выход – на входе программный модуль получает определенный набор исходных данных, выполняет содержательную обработку и возвращает один набор результатных данных, т.е. реализуется стандартный принцип IPO (Input - Process - Output - вход-процесс-выход );

· функциональная завершенность – модуль выполняет перечень регламентированных операций для реализации каждой отдельной функции в полном составе, достаточных для завершения начатой обработки;

· логическая независимость – результат работы программного модуля зависит только от исходных данных, но не зависит от работы других модулей;

· слабые информационные связи с другими программными модулями – обмен информацией между модулями должен быть по возможности минимизирован;

· обозримый по размеру и сложности программный код .

Установить разумные размеры модулей трудно, хотя стоит придерживаться правила: выделять модули, пока это целесообразно . Обычно размеры модуля ограничены несколькими десятками строк кода на языке высокого уровня. Считается, что малый модуль лучше большого, поскольку с увеличением размеров модулей их восприятие и отладка усложняются ускоренными темпами. Кроме того, большие модули часто оказываются взаимозависимыми, и изменения в одном из них влекут необходимость модификации других.

Каждый модуль состоит из спецификации и тела . Спецификации определяют правила использования модуля, а тело – способ реализации процесса обработки.

Принципы модульного программирования программных продуктов во многом сходны с принципами нисходящего проектирования: сначала определяются состав и подчиненность функций, а затем - набор программных модулей, реализующих эти функции.

Однотипные функции реализуются одними и теми же модулями. Функция верхнего уровня обеспечивается главным модулем; он управляет выполнением нижестоящих функций, которым соответствуют подчиненные модули.

При определении набора модулей, реализующих функции конкретного алгоритма, необходимо учитывать следующее:

· каждый модуль вызывается на выполнение вышестоящим модулем и, закончив работу, возвращает управление вызвавшему его модулю;

· принятие основных решений в алгоритме выносится на максимально «высокий» по иерархии уровень;

· для использования одной и той же функции в разных местах алгоритма создается один модуль, который вызывается на выполнение по мере необходимости.

В результате дальнейшей детализации алгоритма создается функционально-модульная схема (ФМС ) алгоритма приложения, являющася основой для программирования (рис. 1.2).

Состав и вид программных модулей, их назначение и характер использования в программе в значительной степени определяются инструментальными средствами.

Алгоритмы большой сложности обычно представляются с помощью схем двух видов:

· обобщенной схемы алгоритма – раскрывает общий принцип функционирования алгоритма и основные логические связи между отдельными модулями на уровне обработки информации (ввод и редактирование данных, вычисления, печать результатов и т.п.);


Парадигмы программирования

Мо́дульное программи́рование - это организация программы как совокупности небольших независимых блоков, называемых модулями, структура и поведение которых подчиняются определённым правилам. Использование модульного программирования позволяет упростить тестирование программы и обнаружение ошибок. Аппаратно-зависимые подзадачи могут быть строго отделены от других подзадач, что улучшает мобильность создаваемых программ.

Мо́дуль - функционально законченный фрагмент программы . Во многих языках (но далеко не обязательно) оформляется в виде отдельного файла с исходным кодом или поименованной непрерывной её части. Некоторые языки предусматривают объединение модулей в пакеты .

Энциклопедичный YouTube

  • 1 / 5

    Принцип модульности является средством упрощения задачи проектирования ПС и распределения процесса разработки ПС между группами разработчиков. При разбиении ПС на модули для каждого модуля указывается реализуемая им функциональность, а также связи с другими модулями. . Удобство использования модульной архитектуры заключается в возможности обновления (замены) модуля, без необходимости изменения остальной системы.

    Программный код часто разбивается на несколько файлов, каждый из которых компилируется отдельно от остальных. Такая модульность программного кода позволяет значительно уменьшить время перекомпиляции при изменениях, вносимых лишь в небольшое количество исходных файлов, и упрощает групповую разработку . Также это возможность замены отдельных компонентов (таких как jar-файлы , so или dll библиотеки) конечного программного продукта, без необходимости пересборки всего проекта (например, разработка плагинов к уже готовой программе).

    Одним из методов написания модульных программ является объектно-ориентированное программирование . ООП обеспечивает высокую степень модульности благодаря таким свойствам, как инкапсуляция , полиморфизм и позднее связывание.

    Модульная система модулей

    Несмотря на то, что модульное программирование никак не связано с деталями конкретного языка (и даже в случае отсутствия явной поддержки со стороны языка может применяться при достаточной дисциплине со стороны программистов), большинство языков выдвигают на верхний уровень свою собственную природу системы модулей, словно перенос системы модулей с одного языка на другой был бы невозможен .

    Реализация Лероя сама построена посредством языка модулей ML , а именно в виде функтора , параметризованного данными о ядре языка и описанием его механизма проверки согласования типов . Это значит, что при написании компилятора некоторого языка достаточно описать ядро языка и передать его данному функтору (как библиотечной функции) - в результате получится компилятор расширения известного языка системой модулей ML .

    История концепции модулей

    История концепции модулей как единиц компиляции восходит к языкам Фортран II и Кобол , то есть, к концу 1950-х годов . В 1976 году появилась публикация, в которой была развита концепция модульности - о языке Mesa (англ. ) , который был разработан в Xerox PARC . В 1977 году подробно ознакомился с этой концепцией учёный Никлаус Вирт , общаясь с разработчиками в Xerox PARC. Эти идеи были использованы Виртом при создании языка Модула-2 , публикация о котором вышла в 1977 году .

    Термин «модуль» в программировании начал использоваться в связи с внедрением модульных принципов при создании программ. В 1970-х годах под модулем понимали какую-либо процедуру или функцию, написанную в соответствии с определёнными правилами. Например: «модуль должен быть простым, замкнутым (независимым), обозримым (от 50 до 100 строк), реализующим только одну функцию задачи, имеющим одну входную и одну выходную точку».

    Первым основные свойства программного модуля более-менее чётко сформулировал Д. Парнас (David Parnas) в 1972 году : «Для написания одного модуля должно быть достаточно минимальных знаний о тексте другого». Таким образом, в соответствии с определением, модулем могла быть любая отдельная процедура (функция) как самого нижнего уровня иерархии (уровня реализации), так и самого верхнего уровня, на котором происходят только вызовы других процедур-модулей.

    Таким образом, Парнас первым выдвинул концепцию скрытия информации (англ. information hiding ) в программировании. Однако существовавшие в языках 70-х годов только такие синтаксические конструкции, как процедура и функция, не могли обеспечить надёжного скрытия информации, из-за повсеместного применения глобальных переменных.

    Решить эту проблему можно было только разработав новую синтаксическую конструкцию, которая не подвержена влиянию глобальных переменных. Такая конструкция была создана и названа модулем. Изначально предполагалось, что при реализации сложных программных комплексов модуль должен использоваться наравне с процедурами и функциями как конструкция, объединяющая и надёжно скрывающая детали реализации определённой подзадачи.

    Таким образом, количество модулей в комплексе должно определяться декомпозицией поставленной задачи на независимые подзадачи. В предельном случае модуль может использоваться даже для заключения в него всего лишь одной процедуры, если необходимо, чтобы выполняемое ею локальное действие было гарантировано независимым от влияния других частей программы при любых изменениях.

    Впервые специализированная синтаксическая конструкция модуля была предложена Н. Виртом в 1975 г. и включена в его новый язык Modula . Насколько сильно изменяются свойства языка, при введении механизма модулей, свидетельствует следующее замечание Н.Вирта, сделанное им по поводу более позднего языка Модула-2: «Модули - самая важная черта, отличающая язык Модула-2 от его предшественника Паскаля».

    Реализация в языках программирования

    Языки, формально поддерживающие концепцию модулей: IBM S/360

    После того как начинающий embedder наморгается светодиодом, он непременно решит написать нечто более серьезное и у него как у любого начинающего будет только одно желание «чтобы всё быстрее заработало!!!». В такой попытке самоутвердиться он будет писать всё в один файл, не задумываясь о структуре программы, но через некоторое время, когда часть задуманного будет реализована, станет понятно, что чем больше становится программа, тем тяжелее в ней что-либо найти. Это натолкнет его на мысль, что у программы должна быть структура и он пойдёт на просторах интернета смотреть, как решают этот вопрос другие embedder"ы. Посмотрев, как выглядят программы, написанные другими людьми, он сделал вывод, что программу разбивают на файлы, которые представляют законченные логические единицы, что часть функции и переменных выносят в хэдер и ещё много чего. Всё вышеописанное - мой опыт, но все начинающие проходят один и тот же путь, поэтому опишу здесь, то с чем столкнулся сам.

    Предположим у нас есть программа, которая выводит температуру на lcd дисплей и что для lcd дисплея, что для датчика температуры(ds18b20 ), нужна инициализация, а также функции для работы с ними. Поэтому логично будет создать два отдельных файла lcd.c и ds18b20.с , которые будут содержать в себе функции необходимые для работы. Такие файлы называют модулями , хотелось бы отметить, что каждый модуль представляет собой независимую, логически-завершенную единицу, которую можно компилировать отдельно от остальной программы . При компиляции модуля компилятор сделает из него объектный файл.

    Следующий вопрос, который возникает, раз модуль это независимая структура, можно сказать «вещь в себе », то она не имеет связи с внешним миром, а нас это не устраивает. Для связи модулей с внешним миром используется заголовочные файлы, их также называют хэдерами/хидерами и они имеют расширение .h . Назвать хэдер можно как угодно, но удобнее, чтобы его название совпадало с названием модуля, lcd.h и ds18b20.h , также надо сказать, что все подключаемые файлы(#include ) удобно вынести в хэдер и подключать только его вначале модуля.
    Когда хэдера не было, начало lcd.с выглядело так
    #define F_CPU 8000000UL #include #include
    а после создание хэдера стало выглядеть так
    #include

    Но тут же возникает еще один вопрос, что выносить в хэдер?
    В хэдер необходимо вынести прототипы функций, которые могут понадобиться в других модулях программы . Прототип функции лишь объявляет функцию и не содержит тела функции, но посмотрев на него можно узнать имя функции, количество, тип аргументов и возвращаемый тип данных .
    В файле lcd.h
    void lcd_init(void); void lcd_write_symbol(char symbol); void lcd_write_string(char *str); void lcd_clear(void);
    В файле ds18b20.h будут объявлены следующие прототипы:
    void ds18b20_init(void); uint8_t ds18b20_get_temperature(void);

    Что касается макросов, можно вынести макросы, отвечающие за выполнение условной компиляции
    #define MAKE_CALIBRATION //раскомментировать для калибровки
    А где-то в коде есть конструкция, которая выполняется если предыдущая строчка раскомментирована
    #ifdef MAKE_CALIBRATION touch_x -= 300; touch_x = 240 - touch_x/((Xmax-Xmin)/240); touch_y -= 350; touch_y = 320 - touch_y/((Ymax-Ymin)/320); #endif
    Также можно вынести макросы, отвечающие за выбор выводов, к которым будет подключаться периферия
    #define D0 PORTA //так данные передаются по 16 битной шине, #define D7 PORTD //под это дело мы используем два порта

    Но в то же время в хэдере не надо размещать то, что не понадобится в других модулях:

    • макросы типа
      #define (LCD_PIN & 0X80) check_busy_flag
    • переменные, которые будут использоваться только внутри модуля с ключевым словом static
    • переменные с квалификатором extern
    • прототипы функций, которые нужны для каких-то промежуточных действий, например, функцию, которая переводит число в BCD формат

    Теперь пару слов про подключение хэдеров, при программировании микроконтроллеров AVR почти во всех модулях подключается хэдер для работы с портами ввода-вывода.
    #include avr/io.h
    То есть он подключается в lcd.h и в ds18b20.h , теперь если мы подключим эти два заголовка в основном файле программы, то avr/io.h подключится дважды, хотя достаточно было и одного . Для того чтобы избежать возникновения такой ситуации и хэдер не подключился дважды используют #include guards , который выглядит следующим образом.
    #ifndef _ENCODER_H_ #define _ENCODER_H_ // оформляем как обычный хэдер #endif
    Это конструкция позволяет препроцессору определить, что данный хэдер уже включался в программу и не включать его повторно. Подробнее про это можно почитать .
    Также ограничить количество подключений файла до одного, можно с помощью конструкции
    #pragma once // оформляем как обычный хэдер
    Преимущество использование #pragma once вместо #include guards можно почитать .
    Кстати подключать можно не только хэдеры, а также файлы с расширением , если это надо
    #include “font.c”
    В данном случае для вывода букв на TFT дисплей подключается файл с шрифтами.
    На этом всё, мне кажется это минимум, который необходимо знать каждому начинающему программисту микроконтроллеров.

    В любой профессии, не только в программировании, вы переживаете разные эмоциональные состояния по ходу выполнения проекта:

    • Сначала есть энтузиазм от перспектив и возможностей.
    • Затем приходит азарт. Первые ошибки и трудности вас только раззадоривают, заставляя мозг и фантазию работать на полную катушку.
    • Следом проседает концентрация. В какой-то момент вы перестаёте обращать внимание на предупреждения и мелкие ошибки, откладывая решение этих проблем на потом.
    • В итоге вы теряете мотивацию. Вы исправляете одну ошибку – появляется три. Вы пытаетесь добавить новую функцию, но выкидываете идею в мусорное ведро из-за нежелания тратить на это много времени.

    Некоторые думают, что это нормально: стоит смириться и каждый раз проживать этот цикл. На деле же всё немного проще, и решение лежит не в области психологии, а в подходе к созданию кода.

    Классическая проблема программирования

    В западной литературе существует термин «big ball of mud» для описания архитектуры программы. Давайте переведём его дословно. Графически «большой шар грязи» можно представить в виде точек на окружности, символизирующих функциональные элементы, и прямых – связей между ними:

    Похоже на ваши глаза перед сдачей проекта, не так ли?

    Это иллюстрация той сложности, с которой вам надо работать, какое количество связей учитывать, если возникает ошибка.

    Программирование не уникальная дисциплина: здесь можно и нужно применять опыт из других областей. Возьмём, к примеру, компьютер. Их производители не задумываются над многообразием задач, которые решает пользователь, и уж тем более не выделяют под каждую маленький процессор и память. Компьютер – это просто набор независимых сложных объектов, объединённых в одном корпусе при помощи разъёмов и проводов. Объекты не уникальны, не оптимизированы конкретно под вас, и тем не менее блестяще справляются со своей задачей.

    В программировании есть точно такие же решения. Например, библиотеки. Они помогают не тратить драгоценное время на изобретение велосипеда. Однако для частных задач библиотеки не эффективны – создание отнимет уйму времени, а при единичной повторяемости эффективность стремится к нулю.

    В этом случае полезнее обратиться к модулям. Модуль – логически завершённый фрагмент кода, имеющий конкретное функциональное назначение. Для взаимодействия модулей используются способы, не позволяющие изменять параметры и функциональность. Плюсы модульного программирования очевидны:

    • Ускорение разработки.
    • Повышение надёжности.
    • Упрощение тестирования.
    • Взаимозаменяемость.

    Модульное программирование крайне эффективно при групповых разработках, где каждый сотрудник может сконцентрироваться только на своём фронте работ и не оглядываться на решения коллег. Однако и в индивидуальном подходе вы получаете, как минимум, вышеописанные преимущества.

    Но не всё так просто.

    Проблемы модульного программирования

    Сама по себе идея использования модулей не сильно упрощает код, важно минимизировать количество прямых связей между ними. Здесь мы подходим к понятию «инверсия управления» (IoC). Упрощённо – это принцип программирования, при котором отдельные компоненты кода максимально изолированы друг от друга. То есть детали одного модуля не должны влиять на реализацию другого. Достигается это при помощи интерфейсов или других видов представления, не обеспечивающих прямого доступа к модульному коду.

    В повседневной жизни таких примеров множество. Чтобы купить билет на самолёт или узнать время вылета, вам не надо звонить пилоту. Чтобы выпить молока, не надо ехать в деревню или на завод и стоять над душой у коровы. Для этого всегда есть посредники.

    В модульном программировании существует три основные реализации:

    • Внедрение зависимостей. Способ, при котором каждый элемент имеет свой интерфейс, взаимодействие модулей происходит через интерфейсы.
    • Фабричный метод. Основывается на существовании некого объекта, предназначенного для создания других объектов. Иначе говоря, введение в программу прототипа, объединяющего общие черты для большинства объектов. Прямого взаимодействия между модулями нет, все параметры наследуются от «завода».
    • Сервисный метод. Создаётся один общий интерфейс, являющийся буфером для взаимодействия объектов. Похожую функцию в реальной жизни выполняют колл-центры, магазины, площадки для объявлений и т.д.

    Несмотря на то, что первая реализация IoC используется чаще всего, для первых шагов в модульном программировании лучше использовать другие два. Причина – простое создание интерфейсов лишь ограничивает доступ к модулям, а для снижения сложности кода необходимо также уменьшить количество связей. Интерфейсы, хаотично ссылающиеся на другие интерфейсы, код только усложняют.

    Для решения этой проблемы необходимо разработать архитектуру кода. Как правило, она схожа с файловой структурой любого приложения:

    Таким образом, поддержка принципов модульного программирования, инверсии управления и четкой архитектуры приложения поможет убить сразу трёх зайцев:

    1. Обеспечить чёткое функциональное разделение кода. При возникновении ошибок можно быстро определить источник, а исправления не приведут к появлению новых сбоев.
    2. Минимизировать количество связей. Это позволит упростить разработку, отдав на откуп нескольким разработчикам разные модули. Или вы сможете самостоятельно разрабатывать каждый блок без оглядки на другие, что тоже экономит время и силы.
    3. Создать иерархию с чёткой вертикалью наследования. Это повышает надёжность кода, так как тестирование провести проще, а результаты информативнее.

    Соблюдение принципа модульности в больших проектах позволит сэкономить время и не расплескать стартовый задор. Более того, у вас получится наконец сосредоточиться на самом интересном – реализации оригинальных задумок в коде. А ведь это именно то, что каждый из нас ищет в программировании.