Модульная структура программы представляет собой древовидную структуру, в узлах которой размещаются программные модули, а направленные дуги показывают статическую подчиненность модулей. Если в тексте модуля имеется ссылка на другой модуль, то их на структурной схеме соединяет дуга, которая исходит из первого и входит во второй модуль. Другими словами, каждый модуль может обращаться к подчиненным ему модулям. При этом модульная структура программной системы, кроме структурной схемы, должна включать в себя еще и совокупность спецификаций модулей, образующих эту систему [37].
Функция верхнего уровня обеспечивается главным модулем; он управляет выполнением нижестоящих функций, которым соответствуют подчиненные модули.
При определении набора модулей, реализующих функции конкретного алгоритма, необходимо учитывать следующее:
1) модуль вызывается на выполнение вышестоящим по иерархии модулем и, закончив работу, возвращает ему управление;
2) принятие основных решений в алгоритме выносится на максимально высокий по иерархии уровень;
Архитектура ПО. Введение
3) если в разных местах алгоритма используется одна и та же функция, то она оформляется в отдельный модуль, который будет вызываться по мере необходимости.
Состав, назначение и характер использования программных модулей в значительной степени определяются инструментальными средствами.
Например, при разработке СУБД используются следующие программные модули:
1) экранные формы ввода и/или редактирования информации базы данных;
4) стандартные средства для обработки информации;
5) меню для выбора функции обработки и др.
Методы разработки при мольном программировании
Спецификация программного модуля состоит из функциональной спецификации модуля, описывающей семантику функций, выполняемых этим модулем по каждому из его входов, и синтаксической спецификации его входов, позволяющей построить на используемом языке программирования синтаксически правильное обращение к нему. Функциональная спецификация модуля определяется теми же принципами, что и функциональная спецификация программной системы.
Существуют разные методы разработки модульной структуры Программы, в зависимости от которых определяется порядок программирования и отладки модулей, указанных в этой структуре. Обычно в литературе обсуждаются два метода [42, 46]: метод восходящей разработки и метод нисходящей разработки.
Метод восходящей разработки
Сначала строится древовидная модульная структура программы. Затем поочередно проектируются и разрабатываются модули программы, начиная с модулей самого нижнего уровня, затем предыдущего уровня и т. д. То есть модули реализуются в таком порядке, чтобы для каждого программируемого модуля были уже запрограммированы все модули, к которым он может обращаться. После того как все модули программы запрограммированы, производится их поочередное тестирование и отладка в таком же восходящем порядке. Достоинство метода заключается в том, что каждый модуль при программировании выражается через уже запрограммированные непосредственно подчиненные модули, а при тестировании использует уже отлаженные модули. Недостатки метода восходящей разработки заключаются в следующем:
Блок-схемы для начинающих (Блок схемы алгоритмов)
• на нижних уровнях модульной структуры спецификации могут быть еще определены не полностью, что может привести к полной переработке этих модулей после уточнения спецификаций на верхнем уровне;
• для восходящего тестирования всех модулей, кроме головного, который является модулем самого верхнего уровня, приходится создавать вызывающие программы, что приводит к созданию большого количества отладочного материала, но не гарантирует, что результаты тестирования верны;
• головной модуль проектируется и реализуется в последнюю очередь, что не дает продемонстрировать его заказчику для уточнения спецификаций.
Метод нисходящей разработки
Как и в предыдущем методе, сначала строится модульная структура программы в виде дерева. Затем проектируются и реализуются модули программы, начиная с модуля самого верхнего уровня — головного, далее разрабатываются модули уровнем ниже и т. д. При этом переход к программированию какого-либо модуля осуществляется только в том случае, если уже запрограммирован модуль, который к нему обращается.
Затем производится их поочередное тестирование, и отладка в таком нисходящем порядке. При таком порядке разработки программы вся необходимая глобальная информация формируется своевременно, т. е. ликвидируется весьма неприятный источник просчетов при программировании модулей.
Существенно облегчается и тестирование модулей, производимое при нисходящем тестировании программы. Первым тестируется головной модуль программы, который представляет всю тестируемую программу, при этом все модули, к которым может обращаться головной, заменяются их имитаторами (так называемыми «заглушками» [45]).
Каждый имитатор модуля является простым программным фрагментом, реализующим сам факт обращения к данному модулю с необходимой для правильной работы программы обработкой значений его входных параметров и с выдачей, если это необходимо, подходящего результата. Далее производится тестирование следующих по уровню модулей. Для этого имитатор выбранного для тестирования модуля заменяется самим модулем, и добавляются имитаторы модулей, к которым может обращаться тестируемый модуль. При таком подходе каждый модуль будет тестироваться в «естественных» состояниях информационной среды, возникающих к моменту обращения к этому модулю при выполнении тестируемой программы. Таким образом, большой объем «отладочного» программирования заменяется программированием достаточно простых имитаторов используемых в программе модулей.
Недостатком нисходящего подхода к программированию является необходимость абстрагироваться от реальных возможностей выбранного языка программирования и придумывать абстрактные операции, которые позже будут реализованы с помощью модулей. Однако способность к таким абстракциям является необходимым условием разработки больших программных средств.
Рассмотренные выше методы (нисходящей и восходящей разработок), являющиеся классическими, требуют, чтобы модульная древовидная структура была готова до начала программирования модулей. Как правило, точно и содержательно разработать структуру программы до начала программирования невозможно. При конструктивном и архитектурном подходах к разработке модульная структура формируется в процессе реализации модулей.
Конструктивный подход
Конструктивный подход к разработке программы представляет собой модификацию нисходящей разработки, при которой модульная древовидная структура программы формируется в процессе программирования модуля. Сначала программируется головной модуль, исходя из спецификации программы в целом (спецификация программы является одновременно спецификацией головного модуля). В процессе программирования головного модуля в случае, если эта программа достаточно большая, выделяются подзадачи (некоторые функции) и для них создаются спецификации, реализующих эти подзадачи фрагментов программы. В дальнейшем каждый из этих фрагментов будет представлен поддеревом модулей (спецификация выделенной функции является одновременно спецификацией головного модуля этого поддерева).
Таким образом, на первом шаге разработки программы (при программировании ее головного модуля) формируется верхняя часть дерева, например, как на рис. 3.12
Спецификация программы
Рис. 3.12. Первый шаг формирования модульной структуры программы при конструктивном подходе
По тому же принципу производится программирование следующих по уровню специфицированных, но еще не запрограммированных модулей в соответствии со сформированным деревом. В результате к дереву добавляются очередные уровни, как показано на рис. 3.13.
Рис. 3.13. Второй шаг формирования модульной структуры программы при конструктивном подходе
Архитектурный подход
Архитектурный подход к разработке программы представляет собой модификацию восходящей разработки, при которой модульная структура программы формируется в процессе программирования модуля. Целью разработки в данном методе является повышение уровня языка программирования, а не разработка конкретной программы. Это означает, что для заданной предметной области выделяются типичные функции, специфицируются, а затем и программируются отдельные программные модули, выполняющие эти функции. Сначала в виде модулей реализуются более простые функции, а затем создаются модули, использующие уже имеющиеся функции, и т. д. Это позволяет существенно сократить трудозатраты на разработку конкретной программы путем подключения к ней уже имеющихся и проверенных на практике модульных структур нижнего уровня, что также позволяет бороться с дублированием в программировании. В связи с этим программные модули, создаваемые в рамках архитектурного подхода, обычно параметризуются для того, чтобы облегчить их применение настройкой параметров.
Нисходящая реализация
В классическом методе нисходящей разработки сначала все модули разрабатываемой программы программируются, а затем тестируются в нисходящем порядке. При этом тестирование и отладка модулей могут привести к изменению спецификации подчиненных модулей и даже к изменению самой модульной структуры программы. В результате может оказаться, что часть модулей вообще не нужна в данной структуре, а часть модулей придется переписывать. Более рационально каждый запрограммированный модуль тестировать сразу же до перехода к программированию другого модуля. Такой метод в литературе получил название метода нисходящей реализации.
Источник: infopedia.su
Вир. Явная схема программы
Основные идеи и подходы, заложенные в среду разработки «Вир» в начале, сохраняются неизменными, это:
- Явная схема программы;
- Сборка программ из бинарных компонент.
- Репозиторий стандартных компонент;
- Независимость программы от ОС.
Разберем подробнее эти идеи и их реализацию.
Явная схема программы
В большинстве случаев, современные программы в бинарной форме слабо структурированы. На стадии проектирования могут быть использованы различные методы представления архитектуры. Далее, вручную или с помощью инструментов, строится исходный текст программы, в котором архитектура программы размазана. И дальше, компилятор, особенно сильно оптимизирующий, удаляет оставшиеся следы архитектуры.
Как правило, из бинарной программы восстановить архитектуру программы невозможно. Более того, так как далее разработка продолжается, как правило, на уровне исходного кода, то соответствие между архитектурой программы и исходным кодом теряется частично или полностью.
Единственным средством явного структурирования программы являются DLL (SO), но использование DLL накладывает существенные ограничения на способы разработки и развития программ. Вплоть до того, что, например, в языке Go постулируется статическая сборка программ, чтобы не сталкиваться с проблемами динамической сборки.
Альтернатива DLL существовала в реализациях модульных языков программирования, например, в системе Оберон [7] или в OS Excelsior iV [8], написанной на языке Модула-2. В обоих случаях, использовалась раздельная компиляция модулей и динамическая загрузка. При запуске программы, которая была представлена головным модулем программы, динамически подгружались используемые (импортируемые) модули, кроме тех, которые уже были загружены.
Схема работающей программы при этом сохранялась в виде таблиц импорта для каждого модуля. Схему можно было извлечь из бинарных образов модулей и симфайлов, в которых хранилась информация об экспорте/импорте каждого модуля.
К сожалению, вместо развития модульных языков, которое могло привести к устранению их недостатков, IT пошло по пути использования слабоструктурированных языков, ярким примеров которых является С++.
Критика слабоструктурированных языков выходит за рамки данной статьи. Замечу лишь то, что использование ООП далеко не всегда увеличивает структурность, а часто приводит к более запутанному и трудно поддерживаемому коду. ООП, исходя из общих соображений на философском уровне, это, безусловно, правильный подход. Но вот современные реализации подкачали, впрочем, некоторый прогресс существует, например, в языке Go.
Более существенное продвижение, на мой взгляд, произойдет, когда мы осознаем, что в большинстве ООП языков реализовано вовсе не object-oriented programming, а class-oriented programming (CLOP) и перейдем к OOP, опираясь на объекты, а не классы. В каком-то смысле аббревиатура CLOP (намекающая на bugs) является шуткой, но в которой, как и в любой шутке, есть доля шутки.
Вернемся к схеме программ, характерной для модульных языков. Для них схема – это дерево, корнем которого является головной модуль программы, а переходы к узлам – это использование модуля (импорт).
Пример модульной схемы, модуль А – головной модуль программы:
Перечислим недостатки модульной схемы:
- Устройство схемы или направление роста дерева. Корень дерева в модульной схеме – это модуль верхнего уровня, то есть модуль, обращенный к пользователю (для нас не важно, пользователь – это человек или программная часть). А это приводит к тому, что при любом изменении функциональности, корень дерева меняется. И это еще полбеды, гораздо хуже, что головной модуль становится узким местом при изменениях программы. Гораздо естественнее для «живой» модифицируемой программы схема, в которой корень находится внизу, и из него разворачивается функциональность.
- Статичность схемы. Во-первых, изменение схемы делается только добавлением импорта в модуль и компиляцией. Динамическое изменение/построение новой схемы невозможно. Во-вторых, для использования компоненты необходимо при разработке иметь доступ к описанию её интерфейса. А если интерфейс изменился, то необходима перекомпиляция. Казалось бы, что этот недостаток преодолевается использованием средств ООП – описанием базового класса и наследованием (и еще нужна фабрика объектов или подобные механизмы). Да, но только частично. Хотя бы базовый класс должен быть описан во время разработки использующей компоненты. А при использовании базового класса мы постоянно сталкиваемся с типичной проблемой CLOP – изменение в базовом классе приводит к перекомпиляции всех наследников. Еще более важно, что как только мы добавили ООП, мы потеряли простоту и ясность модульной схемы.
- Следующий недостаток модульной схемы, о котором подробнее будем говорить позже, это то, что все узлы в модульной схеме одного и того же уровня. Понятно, что модули могут быть разного размера, но это не создает вложенность. В схеме программы должна присутствовать вложенность иерархий (например, дерево деревьев), иначе схема будет пригодна только для простых программ.
Реализуя явную схему программы в среде «Вир» нам удалось избежать перечисленных выше недостатков модульных схем. Рассмотрим подробнее.
Устройство схемы в среде «Вир»
Корень программы – это самая низкоуровневая компонента, содержащая инструменты программы, необходимые для её запуска. Далее разворачивается дерево компонент высокого уровня. Компоненты высокого уровня мы называем «рабочими столами». При этом каждый рабочий стол – это дерево компонент. Таким образом, дерево обращено к пользователю кроной.
Ближе к корню располагаются более «сервисные» компоненты. Ближе к кроне компоненты, более ориентированные на пользователя, не важно, кто/что является пользователем — человек либо программа.
Принципиально важно то, что схема отделена от компонент. Схема первична, компоненты вторичны. Если у нас есть схема программы, то любой компонент можно заменить на другой без изменения/компиляции компонента. И программа продолжит работать, если эти компоненты, старая и новая, «совместимы» (об этом позже).
Рассмотрим пример схемы программы. На верхнем уровне схема состоит из рабочих столов (РС). Каждый рабочий стол определяется под-деревом.
Схема в «Вире» позволяет добавлять функциональность не только статически, но и «на лету», например, за счет скачивания дополнительных компонент из облака. Например, таким способом реализован механизм подключения «дополнений» (add-ons). Причем, этот механизм работает не для конкретной программы, а может быть применен в любой из создаваемых программ. Кроме механизма подключения «дополнений» таким способом реализовано множество подобных «сервисов».
Взаимодействие компонент
Ранее был упомянут один из недостатков модульной схемы, статичность схемы и статичность подключения компонент через импорт.
Каким образом реализовано взаимодействие компонент в «Вире»? Способ связи компонент, то есть способ, каким компонента подключаются к тем компонентам, которые нужны для её работы, пожалуй, есть самая необычная часть нашей технологии.
Тема 3.3.1 Модульная структура программного продукта
Модульное программирование – это логически взаимосвязанная совокупность функциональных элементов, оформленных в виде отдельных программных моду-лей.
— Один вход и один выход, на входе модуль получает набор исходных данных выполняет обработку и возвращает один набор результатных данных т.е. реализует стандартную функцию Input-Process-Output.
— Функциональная завершенность – модуль выполняет перечень операций для реализации каждой отдельной функции с полном составе.
— Логическая независимость – результат работы программного модуля зависит от исходных данных и не зависит от работы других модулей.
— Слабые информационные связи с другими программными модулями – обмен информацией между модулями должен быть минимальным.
— Обозримый по размеру сложности программный элемент.
Каждый модуль состоит из спецификации и тела. Спецификации определяют пра-вила использования модуля, а тело – способ реализации процесса обработки.
Принципы модульного программирования во многом схожи с нисходящим прое-ктированием, сначала определяется состав и подчиненность функции, а затем на-бор программных модулей реализующих эти функции. Функции верхнего уровня обеспечиваются главным модулем, он управляет выполнением нижестоящих фун-кций, которым соответствуют подчиненные модули. При определении набора модулей необходимо учитывать:
— каждый модуль вызывается на выполнение вышестоящим и закончив работу, возвращает управление вызвавшему его модулю;
— принятие основных решений в алгоритме выносится на максимально высокий уровень по иерархии уровней.
— для использования одной и той же функции в разных шестах создается один модуль, который вызывается на выполнение по мере необходимости.
В результате дальнейшей детализации алгоритма создается функциональная модульная схема (ФМС), которая является основой для программирования.
Рисунок 17 Функционально-модульная структура приложения
Некоторые фунции могут выполняться с помощью одного и того же программного модуля (например, функции Ф1 и Ф2).
* Функция Ф3 реализуется в виде последовательности выполнения программных модулей.
* Функция Фт реализуется с помощью иерархии связанных модулей.
* Модуль п управляет выбором на выполнение подчиненных модулей.
* Функция Фх реализуется одним программным модулем.
Ввод в действие
Структура программных продуктов
Графический интерфейс пользователя
Класс объектов
Источник: studopedia.su