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

К модулю предъявляются следующие требования:

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

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

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

4) модуль должен возвращать управление в точку его вызова, в свою очередь, он должен иметь возможность сам вызывать другие модули;

5) модуль не должен сохранять историю своих вызовов и использовать ее при своем функционировании;

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

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

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

Для достижения независимости модулей часто используется принцип информационной локализованности , который состоит в том, что вся информация о структуре данных, о прототипах функций, констант и т.д. сосредотачивается («упрятывается») в отдельном модуле. Доступ к этой информации осуществляется только через этот модуль (в алгоритмическом языке С/С++ такие модули имеют расширение *.h).

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

Достоинствами модульного программирования является следующее:


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

· можно создавать библиотеки наиболее употребительных модулей;

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

· появляется много естественных контрольных точек для отладки проекта;

· проще проектировать и в дальнейшем модифицировать программы.

Недостатки модульного программирования заключаеются в следующем:

· возрастает размер требуемой оперативной памяти;

· увеличивается время компиляции и загрузки;

· увеличивается время выполнения программы;

· довольно сложными становятся межмодульные интерфейсы.

Модульное программирование реализуется через модули – функции. Функция – это область памяти, выделяемая для сохранения программного кода, предназначенного для выполнения конкретной задачи. Другими словами, функция – минимальный исполняемый модуль программы на языке С/С++. По умолчанию функция имеет тип external, и доступ к ней возможен из любого файла программы. Но она может быть ограничена спецификатором класса памяти static.

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

Тип имя_функции (спецификация_параметров) тело_функции

Тип – это тип возвращаемого функцией значения, в том числе void (кроме типов массива или функции). Умолчанием является тип int. Если тип возврата функции не void, то тело функции должно содержать как минимум один оператор return.

Имя_функции – идентификатор, с помощью которого можно обратиться к функции. Он выбирается программистом произвольно и не должен совпадать со служебными словами и с именами других объектов программы. Однако любая программа на языке С/С++ должна иметь хотя бы одну функцию с именем main – главную функцию, содержащую точку входа в программу.

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

Тип имя_формального_параметра

Тип может быть встроенным (int, long, float, double и т.д.), структурой (struct), объединением (union), перечислением (enum), указателями на них или на функции или классы (class). Имя_формального_параметра представляет собой имя используемой в теле функции переменной. Идентификаторы формальных параметров не могут совпадать с именами локальных переменных, объявленных внутри тела функции.

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

В языке С/C++ допустимы функции, количество параметров у которых при компиляции функции не фиксировано, следовательно, остаются неизвестными и их типы. Количество и типы параметров таких функций становятся известными только при их вызове, когда явно задан список фактических параметров. При определении и описании таких функций со списками параметров неопределенной длины спецификацию формальных параметров следует закончить запятой и многоточием.

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

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

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

Модульное программирование работает по принципу «разделяй и властвуй». Стоит разобраться.

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

Сам термин «модуль» начал использоваться в программировании в связи с внедрением модульного принципа при написании программ. В семидесятых годах модулем называли какую-то функцию или процедуру, которая была написана по определенным правилам. Так как на тот момент не было сформировано общепризнанных требований, то модулем называли любую процедуру, чей размер составлял до пятидесяти строк. Парнасом были сформированы первые конкретизированные требования к модулю: « Для формирования одного модуля должно быть достаточно самых минимальных знаний о содержании другого». Получается, именно Парнас впервые сформировал концепцию скрытия информации в программировании. Его определение приводит нас к тому, что модулем может называться любая отдельная процедура как самого нижнего, так и самого верхнего уровня иерархии. Надежно скрытие информации нельзя было обеспечить посредством использования конструкций, существующих в то время, так как они было подвержены сильному действию глобальных переменных, а их поведение в сложных программах предсказать весьма сложно. Необходимо было создать конструкцию, изолированную от этих переменных. Именно ее и назвали модулем, а на его основе и зародилось модульное программирование.

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

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

Цели урока:

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

ХОД УРОКА

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

1.1. Устный опрос:

  • Какой вид имеет структура описания процедуры и функции в Turbo Pascal?
  • В чем состоит отличие описания процедуры и функции?
  • Что такое область действия идентификаторов?
  • Какие параметры называются формальными и какие фактическими?
  • Какие способы передачи параметров реализованы в Turbo Pascal?
  • Почему при работе с графикой в Turbo Pascal необходимо предложение uses Graph?
  • Какие процедуры и функции модуля Graph вам известны?

2. Изложение нового материала.

2.1.1. Технология модульного программирования.

Языки высокого уровня появились в 60-е годы. Ресурсы ЭВМ (объем ОЗУ 8 Кбайт, быстродействие 20 тыс. операций в сек.) были недостаточны, поэтому программисты вынуждены были писать программы весьма “хитроумно” с использованием оператора безусловного перехода. Программа получалась запутанной, имела структуру “блюдо спагетти”. Так как область применения ЭВМ расширялась, программное обеспечение усложнялось. Программисты, решающие сложные задачи, столкнулись с проблемой разрастания количества и размера программ до такой степени, что дальнейший процесс разработки становился практически неуправляемым, и никто из разработчиков не мог с уверенностью сказать, что созданный программный продукт всегда выполняет то, что требуется, и что он не выполняет ничего такого, что не требуется. Поэтому возникла необходимость в новой методологии разработки программных проектов. В 1968–1969 гг. состоялись конференции по программированию. На второй из них Эдсгер Дийкстра предложил принципиально новый способ создания прграмм – структурное программирование. Главное – разбиение программного комплекса (при его создании) на программные модули, которые соединяются иерархически.

Цели модульного программирования:

1. Улучшать читабельность программ.
2. Повышать эффективность и надежность программ (легко находить и корректировать ошибки).
3. Уменьшать время и стоимость программной разработки (уменьшается время отладки).

Разбиение программного комплекса на модули выполняется в соответствии со следующими принципами:

  1. Модуль – это независимый блок, код которого физически и логически отделен от кода других модулей.
  2. Размер модуля не больше 100 операторов.
  3. Имеет одну входную и одну выходную точку.
  4. Модули связаны иерархически.
  5. Разбиение должно обеспечивать надежное скрытие информации в модуле.
    Парнас: “Для написания одного модуля должно быть достаточно минимальных знаний о тексте другого”.
  6. Каждый модуль должен начинаться с комментария (его назначение – входные и выходные переменные).
  7. Не использовать метки и оператор GOTO.
  8. Использовать только стандартные управляющие конструкции (условие, выбор, цикл, блок).

2.1.2. Нисходящее и восходящее программирование .

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

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

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

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

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

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

Преимущество восходящего программирования – не нужно писать заглушки.

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

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

2.1.3. Оформление программы в виде модуля.

При подключении стандартных модулей достаточно корректно записать их идентификаторы в предложении uses. При разработке собственных модулей необходимо помнить некоторые особенности:

  • Не допускается одновременное использование модулей с одинаковыми именами.
  • Идентификатор модуля, указанный в заголовке (unit ), должен совпадать с именами файлов, содержащих исходный (.pas ) (.tpu , . tpp , .tpw ) код.
  • Если идентификатор модуля длиннее восьми символов, то он должен совпадать с именами файлов по первым восьми символам.

Общая структура модуля

unit идентификатор модуля;

{Интерфейсный раздел }

interface

в этом разделе описывается взаимодействие данного модуля с другими пользовательскими и стандартными модулями, а также с главной программой. Другими словами – взаимодействие модуля с “внешним миром”.

Список импорта интерфейсного раздела

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

Список экспорта интерфейсного раздела

const
type
var
procedure
function

Раздел реализации

Implementation

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

Список импорта раздела реализации

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

Подразделы внутренних для модуля описаний

label
const
type
var
procedure
function

Раздел инициализации

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

2.2. Примеры.

Пример модуля a1

unit a1;
interface
uses graph;
procedure init;
procedure pr1;
implementation
procedure init;
{инициализация графического режима}
procedure pr1;
begin
{команды построения фрагмента изображения}
end;
begin
init;
pr1;
readln;
end.

Головной модуль

program a;
uses a1, a2, a3, a4;
begin
pr1;
pr2;
pr3;
pr4;
readln;
end.

3. Проверка качества усвоения нового материала.

3.1. Устный опрос .

  • Назовите принципы модульного программирования.
  • Когда применяется технология нисходящего программирования? А восходящего?
  • В чем различие между технологией восходящего и технологией нисходящего программирования?
  • Какие существуют особенности при разработке собственных модулей?
  • Из каких разделов состоит модуль?
  • Что описывается в разделе interface?
  • Что описывается в разделе implementation?
  • Что описывается в разделе инициализации?

3.2. Самостоятельная работа учащихся на уроке .

Учащиеся разбиты на две группы. Работой каждой группы руководит “начальник”. Получив задание, учащиеся начинают коллективную работу. Каждый ученик разрабатывает свою программу, оформляет ее в виде модуля UNIT и отдает “начальнику”, который пишет головную программу, объединяя модули своих “подчиненных”.

Литература:

  1. Марченко А. И., Марченко Л. М. “Программирование в среде Turbo Pascal 7.0”, М.: “Бином Универсал”, 1998.
  2. Информатика. № 2. /Приложение к газете “Первое сентября”, 1996.

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

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

Наряду с этими преимуществами имеются и некоторые недостатки, которые могут привести к возрастанию стоимости программной системы:

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

Перечислим основные свойства и требования к модулям .

  • 1. Модуль возникает в результате сепаратной компиляции или является частью результата совместной компиляции. Он может активизироваться операционной системой или быть подпрограммой, вызываемой другим модулем.
  • 2. На содержимое модуля можно ссылаться с помощью его имени.
  • 3. Модуль должен возвращать управление тому, кто его вызвал.
  • 4. Модуль может обращаться к другим модулям.
  • 5. Модуль должен иметь один вход и один выход. Иногда программа с несколькими входами может оказаться короче и занимать меньше места в памяти. Однако опыт модульного программирования показал, что разработчики предпочитают иметь несколько похожих модулей, но не использовать несколько входов и выходов в одном модуле. Это объясняется тем, что единственность входа и выхода гарантирует замкнутость модуля и упрощает сопровождение программной системы.
  • 6. Модуль сравнительно невелик. Размеры модуля влияют на степень независимости элементов программы, легкость ее чтения и тестирования. Обнаружено, что небольшие модули позволяют строить такие программы, которые легче изменять. Такие модули чаще используются, они облегчают оценку и управление разработкой, их можно рекомендовать и достаточно опытным, и неопытным программистам. Можно было бы удовлетворить критериям высокой прочности и минимального сцепления, спроектировав программу как несколько больших модулей, но вряд ли таким образом была бы достигнута высокая степень независимости. Как правило, модуль должен содержать от 10 до 100 операторов языка высокого уровня (в некоторых публикациях - до 200).

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

  • 7. Модуль не должен сохранять историю своих вызовов для управления своим функционированием. Такой модуль называют предсказуемым. Модуль, хранящий следы своих состояний при последовательных вызовах, не является предсказуемым. Все модули ПС должны быть предсказуемыми, т.е. не должны сохранять никакой информации о предыдущем вызове. Хитрые, неуловимые, зависящие от времени ошибки возникают в тех программах, которые пытаются многократно вызвать непредсказуемый модуль.
  • 8. Структура принятия решения в модуле должна быть организована таким образом, чтобы те модули, на которые прямо влияет принятое решение, были подчиненными (вызываемыми) по отношению к принимающему решение модулю. Таким образом, обычно удается исключить передачу специальных параметров-индикаторов, представляющих решения, которые должны быть приняты, а также принимать влияющие на управление программой решения на высоком уровне в иерархии программы.
  • 9. Минимизация доступа к данным. Объем данных, на которые модуль может ссылаться, должен быть сведен к минимуму. Исключение сцепления по общей области, внешним данным и по формату - хороший шаг в этом направлении. Проектировщик должен попытаться изолировать сведения о любой конкретной структуре данных или записи в базе данных в отдельном модуле (или небольшом подмножестве модулей) -возможно, за счет использования информационно прочных модулей.
  • 10. Внутренние процедуры. Внутренняя процедура, или подпрограмма, - это замкнутая подпрограмма, физически расположенная в вызывающем ее модуле. Таких процедур следует избегать по нескольким причинам. Внутренние процедуры трудно изолировать для тестирования, и они не могут быть вызваны из модулей, отличных от тех, которые их содержат. Это не соответствует идее повторного использования. Конечно, имеется альтернативный вариант - включить копии внутренней процедуры во все модули, которым она нужна. Однако это часто приводит к ошибкам (копии часто становятся «не совсем точными») и усложняет сопровождение программы, поскольку, когда процедура изменяется, все модули нужно перекомпилировать.

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

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

Процедурный (или структурный - по названию подхода);

Объектный.

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

Результатом объектной декомпозиции является совокупность объектов, которые затем реализуют как переменные некоторых специально разрабаты­ваемых типов (классов), представляющих собой совокупность полей данных и методов, работающих с этими полями.

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

Модули. Модулем называют автономно компилируемую программную единицу. Термин «модуль» традиционно используется в двух смыслах. Пер­воначально, когда размер программ был сравнительно невелик, и все подпро­граммы компилировались отдельно, под модулем понималась подпрограмма, т. е. последовательность связанных фрагментов программы, обращение к которой выполняется по имени. Со временем, когда размер программ значи­тельно вырос, и появилась возможность создавать библиотеки ресурсов: кон­стант, переменных, описаний типов, классов и подпрограмм, термин «мо­дуль» стал использоваться и в смысле автономно компилируемый набор про­граммных ресурсов.

Данные модуль может получать и/или возвращать через общие области памяти или параметры.

Первоначально к модулям (еще понимаемым как подпрограммы) предъ­являлись следующие требования:

Отдельная компиляция;

Одна точка входа;

Одна точка выхода;

Соответствие принципу вертикального управления;

Возможность вызова других модулей;

Небольшой размер (до 50-60 операторов языка);

Независимость от истории вызовов;

Выполнение одной функции.

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



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

отдельно компилируемую библиотеку ресурсов, требование независимости модулей стало основным.

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

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

Меньше вероятность появления новых ошибок при исправлении ста­рых или внесении изменений в программу, т. е. вероятность появления «волнового» эффекта;

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

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

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

Различают пять типов сцепления модулей:

По данным;

По образцу;

По управлению;

По общей области данных;

По содержимому.

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

Например, функция Мах предполагает сцепление по данным через па­раметры скалярного типа:

Function Max(a, b: integer) : integer;

If a>b then Max:=a else Max: =b;

Сцепление по образцу предполагает, что модули обмениваются данны­ми, объединенными в структуры. Этот тип также обеспечивает неплохие ха­рактеристики, но они хуже, чем у предыдущего типа, так как конкретные пе­редаваемые данные «спрятаны» в структуры, и потому уменьшается «про­зрачность» связи между модулями. Кроме того, при изменении структуры передаваемых данных Необходимо модифицировать все использующие ее модули.

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

Function MaxEl(a:array of integer):integer;

Var i:word;

MaxEl:=a;

for i:=l to High(a) do

if a[i]>MaxEl then MaxEl: =a[i];

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

Например, функция MinMax предполагает сцепление по управлению, так как значение параметра flag влияет на логику программы: если функция MinMax получает значение параметра flag, равное true, то возвращает макси­мальное значение из двух, а если false, то минимальное:

Function MinMax(a, b: integer; flag: boolean): integer;

if(a>b) and (flag) then MinMax: =a

else MinMax: =b;

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

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

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

Например, функция МахА, использующая глобальный массив А, сцеп­лена с основной программой по общей области:

Function MaxA:integer; Var i:word;

MaxA: =a; for i:= Low(a)+l to High(a) do if a[i]>MaxA then MaxA: =a[i];

Следует иметь в виду, что «подпрограммы с памятью», действия кото­рых зависят от истории вызовов, используют сцепление по общей области, что делает их работу в общем случае непредсказуемой. Именно этот вариант используют статические переменные С и C++.

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

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

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

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

Связность модулей. Связность - мера прочности соединения функци­ональных и информационных объектов внутри одного модуля. Если сцепле­ние характеризует качество отделения модулей, то связность характеризует степень взаимосвязи элементов, реализуемых одним модулем. Размещение сильно связанных элементов в одном; модуле уменьшает межмодульные свя­зи и, соответственно, взаимовлияние модулей. В то же время помещение сильно связанных элементов в разные модули не только усиливает межмо­дульные связи, но и усложняет понимание их взаимодействия. Объединение слабо связанных элементов также уменьшает технологичность модулей, так как такими элементами сложнее мысленно манипулировать.

Различают следующие виды связности (в порядке убывания уровня):

Функциональную;

Последовательную;

Информационную (коммуникативную);

Процедурную;

Временную;

Логическую;

Случайную.

При функциональной связности все объекты модуля предназначены для выполнения одной функции (рис. 2.1, а): операции, объединяемые для вы­полнения одной функции, или.данные, связанные с одной функцией. Модуль, элементы которого связаны функционально, имеет четко определенную цель, при его вызове выполняется одна задача, например, подпрограмма по­иска минимального элемента массива. Такой модуль имеет максимальную связность, следствием которой являются его хорошие технологические каче­ства: простота тестирования, модификации и сопровождения. Именно с этим связано одно из требований структурной декомпозиции «один модуль - одна функция».

Из тех же соображений следует избегать неструктурированного распре­деления функции между модулями - библиотеками ресурсов. Например, ес­ли при проектировании текстового редактора предполагается функция редактирования, то лучше организовать модуль - библиотеку функций редактиро­вания, чем поместить часть функций в один модуль, а часть в другой.

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

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

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

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

Временная связность функций подразумевает, что эти функции выпол­няются параллельно или в течение некоторого периода времени (рис. 2.1, д). Временная связность данных означает, что они используются в некотором временном интервале. Например, временную связность имеют функции, вы­полняемые при инициализации некоторого процесса. Отличительной осо­бенностью временной связности является то, что действия, реализуемые та­кими функциями, обычно могут выполняться в любом порядке. Содержание модуля с временной связностью функций имеет тенденцию меняться: в него могут включаться новые действия и/или исключаться старые. Большая веро­ятность модификации функции еще больше уменьшает показатели техноло­гичности модулей данного вида по сравнению с предыдущим.

Логическая связь базируется на объединении данных или функций в од­ну логическую группу (рис. 2.1, е). В качестве примера можно привести функции обработки текстовой информации или данные одного и того же ти­па. Модуль с логической связностью функций часто реализует альтернатив­ные варианты одной операции, например, сложение целых чисел и сложение вещественных чисел. Из такого модуля всегда будет вызываться одна какая-либо его часть, при этом вызывающий и вызываемый модули будут связаны по управлению. Понять логику работы модулей, содержащих логически свя­занные компоненты, как правило, сложнее, чем модулей, использующих вре­менную связность, следовательно их показатели технологичности еще ниже.

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

Обратите внимание, что в трех предпоследних случаях связь между не­сколькими подпрограммами в модуле обусловлена внешними причинами. А в последнем - вообще отсутствует. Это соответствующим образом проециру­ется на технологические характеристики модулей. В табл. 2.2 представлены характеристики различных видов связности по экспертным оценкам .

Анализ табл. 2.2 показывает, что на практике целесообразно использо­вать функциональную, последовательную и информационную связности.

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

Библиотеки ресурсов. Различают библиотеки ресурсов двух типов: библиотеки Подпрограмм и библиотеки классов.

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

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

В качестве средства улучшения технологических характеристик библи­отек ресурсов в настоящее время широко используют разделение тела моду­ля на интерфейсную часть и область реализации (секции Interface и Imple­mentation - в Pascal, h и срр-файлы в C++ и в Java).

Интерфейсная часть в данном случае содержит совокупность объявле­ний ресурсов (заголовков подпрограмм, имен переменных, типов, классов и т. п.), которые данная библиотека предоставляет другим модулям. Ресурсы, объявление которых в интерфейсной части отсутствует, извне не доступны.

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