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

Автор: Пользователь скрыл имя, 25 Февраля 2013 в 03:35, курсовая работа

Краткое описание

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

Файлы: 1 файл

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

— 203.50 Кб (Скачать)

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

3.1. Функциональная декомпозиция

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

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

3.2. Минимизации количества передаваемых параметров

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

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

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

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

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

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

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

Противоположным для  некоторых мнением является то, что действующий механизм пары CALL - RET не перекрывает потребляемое время. По сравнению с инструкциями перехода инструкция CALL выполняется на 30-50% дольше, а RET в среднем длиннее на 1 цикл. Только когда во внимание принимаются накладные расходы передачи параметров, сохранения регистров и т.д., называемые служебными расходами, модульные программы начинают выглядеть медленнее по сравнению с немодульными программами. В дополнение к тому, что модули модульных программ обычно являются более общими, чем их неструктурированные дубликаты, модули модульных программ могут использовать ссылки на память или стек с большей частотой. Дополнительное время, расходуемое на вычисление действительного адреса в теле модуля, может привести к замедлению выполнения конкретного модуля, чем узко закодированная конкретная программа.

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

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

 

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

 

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

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

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

4.1. Метод восходящей разработки

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

4.2. Метод нисходящей разработки

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

4.3. Конструктивный и архитектурный подходы

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

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

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

Информация о работе Модульное программирование