Дайте понятие и охарактеризуйте модульное программирование. Модульное программирование — ПИЭ.Wiki. Минимизации количества передаваемых параметров

Материал из ПИЭ.Wiki

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

Определение. Что означает модульное программирование?

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

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

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

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

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

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

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

Концепция модульного программирования

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

  • Меньше кода необходимо написать.
  • Код хранится в нескольких файлах.
Как вы организовываете программы среднего или большего размера?

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

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

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

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

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

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

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

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

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

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

Модули служат также целям создания проблемно-ориентированного контекста и локализации машинной зависимости.

Минимизации количества необходимых вызовов

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

Концепция модульного программирования

Концепцию модульного программирования можно сформулировать в виде нескольких понятий и положений:

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

Литература

  1. М.М. Бежанова, Л.А. Москвина. Практическое программирование. Приемы создания программ на языке Паскаль. М.: Научный Мир, 2000, 270 с.
  2. Истомин Е.П., Новиков В.В., Новикова М.В. Высокоуровневые методы информатики и программирования: Учебник. - СПб. ООО "Адреевский издательский дом", 2006 г. - 228 с.

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

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

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

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

Модуль характеризуют:

  1. один вход и один выход - на входе программный модуль получает определенный набор исходных данных, выполняет содержательную обработку и возвращает один набор результатных данных, т.е. реализуется стандартный принцип IPO (Input - Process - Output) - вход-процесс-выход;
  2. функциональная завершенность - модуль выполняет перечень регламентированных операций для реализации каждой отдельной функции в полном составе, достаточных для завершения начатой обработки;
  3. логическая независимость - результат работы программного модуля зависит только от исходных данных, но не зависит от работы других модулей;
  4. слабые информационные связи с другими программными модулями - обмен информацией между модулями должен быть по возможности минимизирован;
  5. обозримый по размеру и сложности программный элемент.

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

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

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

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

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

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

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

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

Но что, если мы хотим скрыть представление структуры данных от клиентов? Листинг 4 показывает интерфейс к модулю, который предоставляет простую очередь приоритетов. Клиент очереди с приоритетом может создавать очереди, регистрировать данные и деактивировать данные в порядке приоритета. Однако нигде в интерфейсе не указана фактическая структура объявленной очереди приоритетов. Где-то, тип должен это выполняется в части реализации очереди приоритетов. Это позволяет нам скрывать представление структуры данных от своих клиентов.

1.3.2. Основные характеристики программного модуля.

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

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

Основные характеристики программного модуля

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

  • хороший модуль снаружи проще, чем внутри;
  • хороший модуль проще использовать, чем построить.

Майерс предлагает использовать более конструктивные характеристики программного модуля для оценки его приемлемости: размер модуля; прочность модуля; сцепление с другими модулями; рутинность модуля (независимость от предыстории обращений к не-му).

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

Методы разработки структуры программы

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

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

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

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

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

В модульных языках программирования как минимум имеются средства для задания функционально прочных модулей (например, модуль типа FUNCTION в языке ФОРТРАН). Средства же для задания информационно прочных модулей в ранних языках программирования отсутствовали - они появились только в более поздних языках. Так в языке программирования Ада средством задания информационно прочного модуля является пакет.

Сцепление модуля - это мера его зависимости по данным от других модулей. Характеризуется способом передачи данных. Чем слабее сцепление модуля с другими модулями, тем сильнее его независимость от других модулей. Для оценки степени сцепления Майерс предлагает упорядоченный набор из шести видов сцепления модулей. Худшим видом сцепления модулей является сцепление по содержимому . Таким является сцепление двух модулей, когда один из них имеет прямые ссылки на содержимое другого модуля (например, на константу, содержащуюся в другом модуле). Такое сцепление модулей недопустимо. Не рекомендуется использовать также сцепление по общей области - это такое сцепление модулей, когда несколько модулей используют одну и ту же область памяти. Такой вид сцепления модулей реализуется, например, при программировании на языке ФОРТРАН с использованием блоков COMMON. Единственным видом сцепления модулей, который рекомендуется для использования современной технологией программирования, является параметрическое сцепление (сцепление по данным по Майерсу) - это случай, когда данные передаются модулю либо при обращении к нему как значения его параметров, либо как результат его обращения к другому модулю для вычисления некоторой функции. Такой вид сцепления модулей реализуется на языках программирования при использовании обращений к процедурам (функциям).

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

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

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

1.3.3. Модульная структура программных продуктов

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

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

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

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

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

  1. экранные формы ввода и/или редактирования информации базы данных;
  2. отчеты генератора отчетов;
  3. макросы;
  4. стандартные процедуры обработки информации;
  5. меню, обеспечивающее выбор функции обработки и др.

1.3.4. Методы разработки структуры программы.

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

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

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

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

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

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

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


Рис. 1.3.1. Первый шаг формирования модульной структуры программы при конструктивном подходе.

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

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


Рис. 1.3.2. Второй шаг формирования модульной структуры программы при конструктивном подходе.

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

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


Рис. 1.3.3. Классификация методов разработки структуры программ.

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

1.3.5. Контроль структуры программы.

Для контроля структуры программы можно использовать три метода:

  • статический контроль,
  • смежный контроль,
  • сквозной контроль.

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

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

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

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